资讯专栏INFORMATION COLUMN

从头实现一个简易版React(二)

vvpvvp / 1797人阅读

摘要:写在开头从头实现一个简易版一地址上一节,我们详细介绍了实现一个简易的思路以及整体的结构,但是对于渲染和更新的原理,却还没有提及,因此,本节我们将重点放在的渲染上。

写在开头

从头实现一个简易版React(一)地址:https://segmentfault.com/a/11...
上一节,我们详细介绍了实现一个简易React的思路以及整体的结构,但是对于渲染和更新的原理,却还没有提及,因此,本节我们将重点放在vDom的渲染上。

进入正题

我们把React元素分为text,basic,custom三种,并分别封装了三种vDom的ReactComponent,用来处理各自的渲染和更新,在这里,我们将重心放在各自ReactComponet的mount方法上。

ReactTextComponent

ReactTextComponent用来处理文本节点,为了标识方便,在返回的内容上加了span标签。

// 用来表示文本节点在渲染,更新,删除时应该做的事情
class ReactTextComponent extends ReactComponent {
  // 渲染
  mountComponent(rootId) {
    this._rootNodeId = rootId
    return `${this._vDom}`
  }
}
//代码地址:src/react/component/ReactTextComponent.js

ReactTextComponent的mount方法非常简单,打上标识符,将内容插入标签内,并把标签内容返回就可以了。

ReactDomComponent

这个类用来处理原生节点的vDom,在将vDom渲染为原生DOM时,要考虑3点:

元素类型

拼凑属性,包含普通属性及事件的处理

子节点的递归渲染

代码如下:

// 用来表示原生节点在渲染,更新,删除时应该做的事情
class ReactDomComponent extends ReactComponent {
  constructor(vDom) {
    super(vDom)
    this._renderedChildComponents = null
  }

  // 渲染
  mountComponent(rootId) {
    this._rootNodeId = rootId

    const { props, type, props: { children = [] } } = this._vDom,
      childComponents = []

    // 设置tag,加上标识
    let tagOpen = `${type} data-reactid=${this._rootNodeId}`,
      tagClose = `/${type}`,
      content = ""

    // 拼凑属性
    for (let propKey in props) {
      // 事件
      if (/^on[A-Za-z]/.test(propKey)) {
        const eventType = propKey.replace("on", "")
        $(document).delegate(`[data-reactid="${this._rootNodeId}"]`, `${eventType}.${this._rootNodeId}`, props[propKey])
      }

      // 普通属性,排除children与事件
      if (props[propKey] && propKey !== "children" && !/^on[A-Za-z]/.test(propKey)) {
        tagOpen += ` ${propKey}=${props[propKey]}`
      }
    }

    // 获取子节点渲染出的内容
    children.forEach((item, index) => {
      // 再次使用工厂方法实例化子节点的component,拼接好返回
      const childComponent = instantiateReactComponent(item)
      childComponent._mountIndex = index

      childComponents.push(childComponent)

      // 子节点的rootId是父节点的rootId加上索引拼接的值
      const curRootId = `${this._rootNodeId}.${index}`
      // 得到子节点的渲染内容
      const childMarkup = childComponent.mountComponent(curRootId)
      // 拼接
      content += childMarkup

      // 保存所有子节点的component
      this._renderedChildComponents = childComponents
    })

    return `<${tagOpen}>${content}<${tagClose}>`
  }
}
//代码地址:src/react/component/ReactDomComponent.js

在React的官方实现中,自己实现了一套事件系统,这里用了jQuery的事件代替。
在样式上,需要基于传入的style对象创建样式,这里也暂时忽略了。

ReactCompositComponent

在创建自定义组件时,通常会这样创建

import React from "react"

class App extends React.Component {
  render() {
    return (
       
    )
  }
}

所以,第一步,我们先实现Component这个父类

// 所有自定义组件的父类
class Component {
  constructor(props) {
    this.props = props
  }

  setState(newState) {
    this._reactInternalInstance.updateComponent(null, newState)
  }
}
//代码地址:src/react/Component.js

Component类上我们主要实现了setState方法,至于有什么用,我们放在更新里说。
在自定义组件的vDom中,type保存的是我们创建的Component的引用,所以在ReactCompositeComponent的mount方法中。我们首先根据vDom的type创建组件的实例,在以此调用它初始渲染的生命周期方法,render方法。
在render方法中,返回了组件渲染内容的vDom,我们根据这个vDom创建它的ReactComponent并调用mount(),就得到了真实的渲染内容。
贴代码:

export default class extends ReactComponent {
  constructor(element) {
    super(element)
    // 存放对应的组件实例
    this._instance = null
    this._renderedComponent = null
  }

  // 渲染
  mountComponent(rootId) {
    this._rootNodeId = rootId
    const { type: Component, props } = this._vDom

    // 获取自定义组件的实例
    const inst = new Component(props)
    this._instance = inst

    // 保留对当前component的引用,下面更新时会用到
    inst._reactInternalInstance = this

    inst.componentWillMount && inst.componentWillMount()

    // 调用自定义组件的render方法,返回一个Vdom
    const renderedVdom = inst.render()

    // 获取renderedComponent的component
    const renderedComponent = instantiateReactComponent(renderedVdom)
    this._renderedComponent = renderedComponent

    // 得到渲染之后的内容
    const renderMarkup = renderedComponent.mountComponent(this._rootNodeId)

    // 在React.render方法最后触发了mountReady事件,所在在这里监听,在渲染完成后触发
    $(document).on("mountReady", () => {
      inst.componentDidMount && inst.componentDidMount()
    })

    return renderMarkup
  }
}
// 代码地址:src/react/component/ReactCompositeComponent.js

从这里可以看出,自定义组件的mount方法并不负责具体的渲染,这些都交给了它的render,它把重心放在了创建对象和调用生命周期上。

总结

文章到这,我们的简易版react已经初步实现了虚拟DOM的创建,生命周期的调用,虚拟DOM的递归渲染和事件处理。
总结一下,每一个vDom都有ReactComponent相对应,递归渲染的本质无非就是获取每个vDom的ReactComponent,并调用它的mount方法。
以上就是整个渲染的思路,下节我们将实现它的diff算法以及更新。
下一节地址:https://segmentfault.com/a/11...

参考资料,感谢几位前辈的分享:
https://www.cnblogs.com/sven3...
https://github.com/purplebamb...
陈屹 《深入React技术栈》

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93110.html

相关文章

  • 从头实现一个简易React(三)

    摘要:写在开头从头实现一个简易版二地址在上一节,我们的已经具备了渲染功能。参考资料,感谢几位前辈的分享陈屹深入技术栈 写在开头 从头实现一个简易版React(二)地址:https://segmentfault.com/a/11...在上一节,我们的react已经具备了渲染功能。在这一节我们将着重实现它的更新,说到更新,大家可能都会想到React的diff算法,它可以说是React性能高效的保...

    yvonne 评论0 收藏0
  • 从头实现一个简易React(一)

    摘要:既然看不懂,那就看看社区前辈们写的一些源码分析文章以及实现思路吧,又这么过了几天,总算是摸清点思路,于是在参考了前辈们的基础上,实现了一个简易版的。总结以上就是实现一个的总体思路,下节我们重点放在不同的上。 写在开头 工作中使用react也很长一段时间了,虽然对它的用法,原理有了一定的了解,但是总感觉停留在表面。本着知其然知其所以然的态度,我试着去看了react源码,几天下来,发现并不...

    meislzhua 评论0 收藏0
  • 基于react native的登录界面demo 超简易教程 redux

    摘要:登录视图登陆失败用户名或密码不能为空弹出提示框成功是点击登录按钮后调用的函数,这里的功能比较简单。通过把发出去密码登录声明组件需要整个中的哪一部分数据作为自己的将和组件联系在一起编写是负责生成的,所以在大项目中还会用到合并。 本猪说 本猪猪刚学react,也刚看RN,就叫写这个,苦不堪言,搭环境就搭了好久。看网上教程也是改了好多小地方才写完了。本着雷锋精神手把手教你写(假的)。 sho...

    scq000 评论0 收藏0
  • 关于Vue2一些值得推荐的文章 -- 五、六月份

    摘要:五六月份推荐集合查看最新的请点击集前端最近很火的框架资源定时更新,欢迎一下。苏幕遮燎沈香宋周邦彦燎沈香,消溽暑。鸟雀呼晴,侵晓窥檐语。叶上初阳乾宿雨,水面清圆,一一风荷举。家住吴门,久作长安旅。五月渔郎相忆否。小楫轻舟,梦入芙蓉浦。 五、六月份推荐集合 查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 苏...

    sutaking 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<