本系列文章在实现一个 cpreact 的同时帮助大家理顺 React 框架的核心内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/PureComponent/HOC/…) 项目地址
- 从 0 到 1 实现 React 系列 —— JSX 和 Virtual DOM
- 从 0 到 1 实现 React 系列 —— 组件和 state|props
- 从 0 到 1 实现 React 系列 —— 生命周期和 diff 算法
- 从 0 到 1 实现 React 系列 —— 优化 setState 和 ref 的实现
- 从 0 到 1 实现 React 系列 —— PureComponent 实现 && HOC 探幽
生命周期
先来回顾 React 的生命周期,用流程图表示如下:
该流程图比较清晰地呈现了 react 的生命周期。其分为 3 个阶段 —— 生成期,存在期,销毁期。
因为生命周期钩子函数存在于自定义组件中,将之前 _render 函数作些调整如下:
1 | // 原来的 _render 函数,为了将职责拆分得更细,将 virtual dom 转为 real dom 的函数单独抽离出来 |
我们可以在 setProps 函数内(渲染前)加入 componentWillMount
,componentWillReceiveProps
方法,setProps 函数如下:
1 | function setProps(component) { |
而后我们在 renderComponent 函数内加入 componentDidMount
、shouldComponentUpdate
、componentWillUpdate
、componentDidUpdate
方法
1 | function renderComponent(component) { |
测试生命周期
测试如下用例:
1 | class A extends Component { |
页面加载时输出结果如下:
1 | componentWillMount |
点击按钮时输出结果如下:
1 | shouldComponentUpdate |
diff 的实现
在 react 中,diff 实现的思路是将新老 virtual dom 进行比较,将比较后的 patch(补丁)渲染到页面上,从而实现局部刷新;本文借鉴了 preact 和 simple-react 中的 diff 实现,总体思路是将旧的 dom 节点和新的 virtual dom 节点进行了比较,根据不同的比较类型(文本节点、非文本节点、自定义组件)调用相应的逻辑,从而实现页面的局部渲染。代码总体结构如下:
1 | /** |
下面根据不同比较类型实现相应逻辑。
对比文本节点
首先进行较为简单的文本节点的比较,代码如下:
1 | // 对比文本节点 |
对比非文本节点
对比非文本节点,其思路为将同层级的旧节点替换为新节点,代码如下:
1 | // 对比非文本节点 |
对比自定义组件
对比自定义组件的思路为:如果新老组件不同,则直接将新组件替换老组件;如果新老组件相同,则将新组件的 props 赋到老组件上,然后再对获得新 props 前后的老组件做 diff 比较。代码如下:
1 | // 对比自定义组件 |
遍历对比子节点
遍历对比子节点的策略有两个:一是只比较同层级的节点,二是给节点加上 key 属性。它们的目的都是降低空间复杂度。代码如下:
1 | // 对比子节点 |
测试
在生命周期的小节中,componentWillReceiveProps 方法还未跑通,稍加修改 setProps 函数即可:
1 | /** |
来测试下生命周期小节中最后的测试用例:
- 生命周期测试
- diff 测试
鸣谢
Especially thank simple-react for the guidance function of this library. At the meantime,respect for preact and react