平时写写 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 值相同时则直接递归比较所有子节点。