1、修改index.js
// index.js
import 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-deprecated
componentWillMount() {
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-deprecated
componentWillUpdate(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-deprecated
componentWillMount() {
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-deprecated
componentWillUpdate(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
// 深度比较新旧两个虚拟dom
let 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对应的真实dom
let 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) // 老的真实dom
let newDom = createDom(newVdom) // 新的真实dom
parentDom.replaceChild(newDom, oldDom)
if (oldVdom.classInstance && oldVdom.classInstance.componentWillUnMount) {
oldVdom.classInstance.componentWillUnMount()
}
// 老的有新的也有,并且类型一样,进行深度dom-diff,复用老的dom节点
// 更新自己的属性、另一方面要比较儿子们
} else {
// todo
return newVdom
}
}
findDom方法实现递归查找真实dom。
/**
* 查找虚拟dom的真实dom
* @param {*} vdom
*/
function findDom(vdom) {
let { type } = vdom
let dom
if (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/