
1、修改index.js
// index.jsimport React from 'react';import ReactDOM from 'react-dom';class Counter extends React.Component {static defaultProps = { name: '计数器' }constructor(props) {super(props)this.state = { number: 0 }console.log('Counter 1.constructor初始化')}// eslint-disable-next-line react/no-deprecatedcomponentWillMount() {console.log('Counter 2.conponentWillMount组件将要挂载')}componentDidMount() {console.log('Counter 4.conponentDidMount挂载完成')}handleClick = () => {this.setState({number: this.state.number + 1})}shouldComponentUpdate(nextProps, nextState) {console.log('Counter 5.shouldComponentUpdate询问组件是否需要更新?')return nextState.number % 2 === 0; // 偶true 基 false}// WARNING! To be deprecated in React v17. Use componentDidUpdate instead.// eslint-disable-next-line react/no-deprecatedcomponentWillUpdate(nextProps, nextState) {console.log('Counter 6.componentWillUpdate组件将要更新')}componentDidUpdate(prevProps, prevState) {console.log('Counter 7.componentDidUpdate组件更新完成')}componentWillUnmount(prevProps, prevState) {console.log('Counter 8.componentDidUpdate组件将要卸载')}render() {console.log('Counter 3.render重新计算新的虚拟dom')return (<div id="div"><p>{this.state.number}</p>{this.state.number === 4 ? <div></div> : <ChildCounter count={this.state.number}></ChildCounter>}<button onClick={this.handleClick}>+</button></div>)}}class ChildCounter extends React.Component {// eslint-disable-next-line react/no-deprecatedcomponentWillMount() {console.log('ChildCounter 1.conponentWillMount组件将要挂载')}componentDidMount() {console.log('ChildCounter 3.conponentDidMount挂载完成')}//WARNING! To be deprecated in React v17. Use new lifecycle static getDerivedStateFromProps instead.// eslint-disable-next-line react/no-deprecated// componentWillReceiveProps(nextProps) {// console.log('ChildCounter 4.componentWillReceiveProps组件将要接收新的属性')// }shouldComponentUpdate(nextProps, nextState) {console.log('ChildCounter 5.shouldComponentUpdate询问组件是否需要更新?')return nextProps.count % 3 === 0; // 3的倍数true否则false}// WARNING! To be deprecated in React v17. Use componentDidUpdate instead.// eslint-disable-next-line react/no-deprecatedcomponentWillUpdate(nextProps, nextState) {console.log('ChildCounter 6.componentWillUpdate组件将要更新')}componentDidUpdate(prevProps, prevState) {console.log('ChildCounter 7.componentDidUpdate组件更新完成')}//WARNING! To be deprecated in React v17. Use componentDidMount instead.componentWillUnmount() {console.log('ChildCounter 8.componentWillUnmount组件将要卸载')}render() {console.log('ChildCounter 2.render重新计算新的虚拟dom')return (<div>{this.props.count}</div>)}}ReactDOM.render(<Counter />, document.getElementById('root'));
2、修改更新组件逻辑
之前Component.js文件的forceUpdate是直接从根节点替换,现在要改为新旧dom对比。
//Component.js// 更新组件forceUpdate() {// 执行将要更新if (this.componentWillUpdate) {this.componentWillUpdate()}let newRenderVdom = this.render() //获取新dom// 深度比较新旧两个虚拟domlet currentRenderVdom = compareTwoVdom(this.oldRenderVdom.dom.parentNode, this.oldRenderVdom, newRenderVdom)this.oldRenderVdom = currentRenderVdom// 执行更新后if (this.componentDidUpdate) {this.componentDidUpdate()}}
在挂载类组件时,要把oldRenderVdom的实例挂载到类的实例上。
修改react-dom.js的MountClassComponent方法。也就是this.oldRenderVdom获取的。
compareTwoVdom方法实现。注意:本节未实现todo以下的更新逻辑
/*** 对当前组件进行DOM-DIFF* @param {*} parentDom 当前组价挂载的父真实节点* @param {*} oldVdom 上一次老的虚拟dom* @param {*} newVdom 新的虚拟dom*/export function compareTwoVdom(parentDom, oldVdom, newVdom, nextDom) {// 新老虚拟dom都不存在if (!oldVdom && !newVdom) {return null} else if (oldVdom && !newVdom) {// 先找到此虚拟dom对应的真实domlet currentDom = findDom(oldVdom)if (currentDom) {parentDom.removeChild(currentDom)}if (oldVdom.classInstance && oldVdom.classInstance.componentWillUnMount) {oldVdom.classInstance.componentWillUnMount()}return null// 新建dom节点并且插入} else if (!oldVdom && newVdom) {let newDom = createDom(newVdom)if (nextDom) {parentDom.insertBefore(newDom, nextDom)} else {parentDom.appendChild(newDom)}return newVdom// 老的有新的也有,但是类型不一样} else if (oldVdom && newVdom && (oldVdom.type !== newVdom.type)) {let oldDom = findDom(oldVdom) // 老的真实domlet newDom = createDom(newVdom) // 新的真实domparentDom.replaceChild(newDom, oldDom)if (oldVdom.classInstance && oldVdom.classInstance.componentWillUnMount) {oldVdom.classInstance.componentWillUnMount()}// 老的有新的也有,并且类型一样,进行深度dom-diff,复用老的dom节点// 更新自己的属性、另一方面要比较儿子们} else {// todoreturn newVdom}}
findDom方法实现递归查找真实dom。
/*** 查找虚拟dom的真实dom* @param {*} vdom*/function findDom(vdom) {let { type } = vdomlet domif (typeof type === 'function') {dom = findDom(vdom.oldRenderVdom)} else {dom = vdom.dom}return dom}
vdom.oldRenderVdom哪里来的呢?是挂载类组件和函数组件时放在vdom上的。

3、源代码
本节代码:https://gitee.com/linhexs/react-write/tree/7.DOM-DIFF-INIT/
