image.png

1、修改index.js

  1. // index.js
  2. import React from 'react';
  3. import ReactDOM from 'react-dom';
  4. class Counter extends React.Component {
  5. static defaultProps = { name: '计数器' }
  6. constructor(props) {
  7. super(props)
  8. this.state = { number: 0 }
  9. console.log('Counter 1.constructor初始化')
  10. }
  11. // eslint-disable-next-line react/no-deprecated
  12. componentWillMount() {
  13. console.log('Counter 2.conponentWillMount组件将要挂载')
  14. }
  15. componentDidMount() {
  16. console.log('Counter 4.conponentDidMount挂载完成')
  17. }
  18. handleClick = () => {
  19. this.setState({
  20. number: this.state.number + 1
  21. })
  22. }
  23. shouldComponentUpdate(nextProps, nextState) {
  24. console.log('Counter 5.shouldComponentUpdate询问组件是否需要更新?')
  25. return nextState.number % 2 === 0; // 偶true 基 false
  26. }
  27. // WARNING! To be deprecated in React v17. Use componentDidUpdate instead.
  28. // eslint-disable-next-line react/no-deprecated
  29. componentWillUpdate(nextProps, nextState) {
  30. console.log('Counter 6.componentWillUpdate组件将要更新')
  31. }
  32. componentDidUpdate(prevProps, prevState) {
  33. console.log('Counter 7.componentDidUpdate组件更新完成')
  34. }
  35. componentWillUnmount(prevProps, prevState) {
  36. console.log('Counter 8.componentDidUpdate组件将要卸载')
  37. }
  38. render() {
  39. console.log('Counter 3.render重新计算新的虚拟dom')
  40. return (
  41. <div id="div">
  42. <p>{this.state.number}</p>
  43. {this.state.number === 4 ? <div></div> : <ChildCounter count={this.state.number}></ChildCounter>}
  44. <button onClick={this.handleClick}>+</button>
  45. </div>
  46. )
  47. }
  48. }
  49. class ChildCounter extends React.Component {
  50. // eslint-disable-next-line react/no-deprecated
  51. componentWillMount() {
  52. console.log('ChildCounter 1.conponentWillMount组件将要挂载')
  53. }
  54. componentDidMount() {
  55. console.log('ChildCounter 3.conponentDidMount挂载完成')
  56. }
  57. //WARNING! To be deprecated in React v17. Use new lifecycle static getDerivedStateFromProps instead.
  58. // eslint-disable-next-line react/no-deprecated
  59. // componentWillReceiveProps(nextProps) {
  60. // console.log('ChildCounter 4.componentWillReceiveProps组件将要接收新的属性')
  61. // }
  62. shouldComponentUpdate(nextProps, nextState) {
  63. console.log('ChildCounter 5.shouldComponentUpdate询问组件是否需要更新?')
  64. return nextProps.count % 3 === 0; // 3的倍数true否则false
  65. }
  66. // WARNING! To be deprecated in React v17. Use componentDidUpdate instead.
  67. // eslint-disable-next-line react/no-deprecated
  68. componentWillUpdate(nextProps, nextState) {
  69. console.log('ChildCounter 6.componentWillUpdate组件将要更新')
  70. }
  71. componentDidUpdate(prevProps, prevState) {
  72. console.log('ChildCounter 7.componentDidUpdate组件更新完成')
  73. }
  74. //WARNING! To be deprecated in React v17. Use componentDidMount instead.
  75. componentWillUnmount() {
  76. console.log('ChildCounter 8.componentWillUnmount组件将要卸载')
  77. }
  78. render() {
  79. console.log('ChildCounter 2.render重新计算新的虚拟dom')
  80. return (<div>{this.props.count}</div>)
  81. }
  82. }
  83. ReactDOM.render(<Counter />, document.getElementById('root'));

2、修改更新组件逻辑

之前Component.js文件的forceUpdate是直接从根节点替换,现在要改为新旧dom对比。

  1. //Component.js
  2. // 更新组件
  3. forceUpdate() {
  4. // 执行将要更新
  5. if (this.componentWillUpdate) {
  6. this.componentWillUpdate()
  7. }
  8. let newRenderVdom = this.render() //获取新dom
  9. // 深度比较新旧两个虚拟dom
  10. let currentRenderVdom = compareTwoVdom(this.oldRenderVdom.dom.parentNode, this.oldRenderVdom, newRenderVdom)
  11. this.oldRenderVdom = currentRenderVdom
  12. // 执行更新后
  13. if (this.componentDidUpdate) {
  14. this.componentDidUpdate()
  15. }
  16. }

在挂载类组件时,要把oldRenderVdom的实例挂载到类的实例上。

修改react-dom.js的MountClassComponent方法。也就是this.oldRenderVdom获取的。
image.png

compareTwoVdom方法实现。注意:本节未实现todo以下的更新逻辑

  1. /**
  2. * 对当前组件进行DOM-DIFF
  3. * @param {*} parentDom 当前组价挂载的父真实节点
  4. * @param {*} oldVdom 上一次老的虚拟dom
  5. * @param {*} newVdom 新的虚拟dom
  6. */
  7. export function compareTwoVdom(parentDom, oldVdom, newVdom, nextDom) {
  8. // 新老虚拟dom都不存在
  9. if (!oldVdom && !newVdom) {
  10. return null
  11. } else if (oldVdom && !newVdom) {
  12. // 先找到此虚拟dom对应的真实dom
  13. let currentDom = findDom(oldVdom)
  14. if (currentDom) {
  15. parentDom.removeChild(currentDom)
  16. }
  17. if (oldVdom.classInstance && oldVdom.classInstance.componentWillUnMount) {
  18. oldVdom.classInstance.componentWillUnMount()
  19. }
  20. return null
  21. // 新建dom节点并且插入
  22. } else if (!oldVdom && newVdom) {
  23. let newDom = createDom(newVdom)
  24. if (nextDom) {
  25. parentDom.insertBefore(newDom, nextDom)
  26. } else {
  27. parentDom.appendChild(newDom)
  28. }
  29. return newVdom
  30. // 老的有新的也有,但是类型不一样
  31. } else if (oldVdom && newVdom && (oldVdom.type !== newVdom.type)) {
  32. let oldDom = findDom(oldVdom) // 老的真实dom
  33. let newDom = createDom(newVdom) // 新的真实dom
  34. parentDom.replaceChild(newDom, oldDom)
  35. if (oldVdom.classInstance && oldVdom.classInstance.componentWillUnMount) {
  36. oldVdom.classInstance.componentWillUnMount()
  37. }
  38. // 老的有新的也有,并且类型一样,进行深度dom-diff,复用老的dom节点
  39. // 更新自己的属性、另一方面要比较儿子们
  40. } else {
  41. // todo
  42. return newVdom
  43. }
  44. }

findDom方法实现递归查找真实dom。

  1. /**
  2. * 查找虚拟dom的真实dom
  3. * @param {*} vdom
  4. */
  5. function findDom(vdom) {
  6. let { type } = vdom
  7. let dom
  8. if (typeof type === 'function') {
  9. dom = findDom(vdom.oldRenderVdom)
  10. } else {
  11. dom = vdom.dom
  12. }
  13. return dom
  14. }

vdom.oldRenderVdom哪里来的呢?是挂载类组件和函数组件时放在vdom上的。
image.png

image.png

3、源代码

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