平时写写 react,却不了解内部是怎么把 jsx 转化为 vdom,然后渲染在界面上,以及当数据流更新时,视图又是怎么更新的呢。

于是我查阅了大量资料后,自己手写了一个简单版的 react,从中大概能了解到 react 基本的运行机制。

react 一个很方便之处是我们可以像写原生 html 那样写组件,这就是 jsx 语法,那么 jsx 是如何转化为 dom 的呢。首先通过 babel 语法树解析转化成为 vdom,它的结构大概是

{
    type: 'div',
    props: { id: 'container' },
    children: ['xxxxx']
}  

之后通过 react 内部的 render 方法将 vdom 转为 dom 节点。

render 方法实现如下:

const _render = (vdom, parent = null) => {
    // custom component 经过 babel 转义 然后 React.createElement 返回的 vdom.type 类型是 function
    // <p id="label">title</p> vdom = { type: 'p', props: {id: 'label'}, children: ['title']}
    const mount = parent ? (el => parent.appendChild(el)) : (el => el);
    if (typeof vdom === 'string' || typeof vdom === 'number') {
      return mount(document.createTextNode(vdom));
    } if (typeof vdom === 'boolean' || vdom === null) {
      return mount(document.createTextNode(''));
    } if (typeof vdom === 'object' && typeof vdom.type === 'function') {
      return Component.render(vdom, parent);
    } if (typeof vdom === 'object' && typeof vdom.type === 'string') {
      const dom = mount(document.createElement(vdom.type));
      for (const child of [].concat(...vdom.children)) _render(child, dom);
      for (const prop in vdom.props) {
        if (Object.prototype.hasOwnProperty.call(vdom.props, prop)) {
          setAttribute(dom, prop, vdom.props[prop]);
        }
      }
      return dom;
    }
    throw new Error(`Invalid VDOM: ${vdom}.`);
  };

值得一提的是在 ReactDOM.render() 时,首先会遍历所有节点,然后实例化节点,调用 componentWillMount 方法,接着 调用内部的 render 将 vdom 转化为 真实 dom,接受一个 标识符 key 值,这在更新组件时将会派上用场。紧接着调用 componentDidMount 方法。

接下来讲一下 react 更新 state 时发生了什么。众所周知,传统的比较两棵树是否相同的时间复杂度是 O(n^3),而 react 基于一套比较规则将时间复杂度降到了 O(n),这大大提高了计算的时间,提高了渲染的速度。因此 react 在更新状态时的 patch 方法都做了什么。其实就是基于 react 的比较算法:1. 两棵树的节点类型都不同时则整棵树都替换;2.当节点的 key 值相同时则直接递归比较所有子节点。

详细实现参见: https://github.com/Vxee/like-react

06-16 05:13