1、修改react.js文件

修改处理文本节点方式,统一转换为对象:
image.png
src/utils.js文件wrapToVdom方法:
image.png
src/constants.js文件REACT_TEXT方法:
image.png

2、修改react-dom.js文件

2.1 修改createDom方法

修改文本处理方式:

  1. /**
  2. * 虚拟dom变为真实dom
  3. * @param {*} vdom 虚拟dom
  4. */
  5. export function createDom(vdom) {
  6. // 否则它就是一个react元素了
  7. const { type, props } = vdom
  8. let dom
  9. if (type === REACT_TEXT) {
  10. dom = document.createTextNode(props.content)
  11. } else if (typeof type === 'function') {
  12. // 类组件
  13. if (type.isReactComponent) {
  14. return mountClassComponent(vdom)
  15. } else {
  16. // 函数组件直接返回
  17. return mountFunctionComponent(vdom)
  18. }
  19. } else {
  20. // 原生组件
  21. dom = document.createElement(type)
  22. }
  23. // 将虚拟dom属性更新到真实dom上
  24. updateProps(dom, {}, props)
  25. // 如果儿子是一个对象,并且是一个虚拟dom,递归调用render
  26. if (typeof props?.children === 'object' && props?.children?.type) {
  27. // 把儿子变成真实dom,插入到自己身上
  28. render(props?.children, dom)
  29. // 如果儿子是一个数组,说明儿子不止一个
  30. } else if (Array.isArray(props?.children)) {
  31. reconcileChildren(props?.children, dom)
  32. }
  33. // 当根据vdom创建真实dom之后,真实dom挂载在dom属性上
  34. vdom.dom = dom
  35. return dom
  36. }

2.2 修改深度比较方法

updateElement方法:

  1. /**
  2. * 深度比较两个虚拟dom
  3. * @param {*} oldVdom 老的虚拟dom
  4. * @param {*} newVdom 新的虚拟dom
  5. */
  6. function updateElement(oldVdom, newVdom) {
  7. // 文本节点
  8. if (oldVdom.type === REACT_TEXT && newVdom.type === REACT_TEXT) {
  9. let currentDom = newVdom.dom = oldVdom.dom // 复用老的真实dom节点
  10. currentDom.textContent = newVdom.props.content // 直接修改老的dom节点文本
  11. // 先更新属性
  12. } else if (typeof oldVdom.type === 'string') { // 说明是原生div
  13. let currentDom = newVdom.dom = oldVdom.dom //复用老的div的真实dom
  14. updateProps(currentDom, oldVdom.props, newVdom.props) // 更新自己
  15. // 更新儿子 只有原生组件 div span才会深度比较
  16. updateChildren(currentDom, oldVdom.props.children, newVdom.props.children)
  17. } else if (typeof oldVdom.type === 'function') {
  18. if (oldVdom.type.isReactComponent) {
  19. // 更新类组件
  20. updateClassComponent(oldVdom, newVdom)
  21. }else {
  22. // 更新函数组件
  23. updateFunctionComponent(oldVdom, newVdom)
  24. }
  25. }
  26. }

updateChildren方法:

  1. /**
  2. * 深度比较儿子
  3. * @param {*} parentDom 父dom节点
  4. * @param {*} oldVChildren 老的儿子们
  5. * @param {*} newVChildren 新的儿子们
  6. */
  7. function updateChildren(parentDom, oldVChildren, newVChildren) {
  8. // children可能是对象,也可能是数组,如果是对象就转为数组
  9. oldVChildren = Array.isArray(oldVChildren) ? oldVChildren : [oldVChildren]
  10. newVChildren = Array.isArray(newVChildren) ? newVChildren : [newVChildren]
  11. const maxLength = Math.max(oldVChildren.length, newVChildren.length)
  12. for (let i = 0; i < maxLength; i++) {
  13. compareTwoVdom(parentDom, oldVChildren[i], newVChildren[i])
  14. }
  15. }

updateClassComponent方法:

  1. function updateClassComponent(oldVdom, newVdom) {
  2. let classInstance = newVdom.classInstance = oldVdom.classInstance;// 类的实例需要复用
  3. newVdom.oldRenderVdom = oldVdom.oldRenderVdom // 上一次类组件渲染出来的虚拟dom
  4. if (classInstance.componentWillReceivedProps) { // 组件将要接受到新的属性
  5. classInstance.componentWillReceivedProps()
  6. }
  7. // 触发组件更新
  8. classInstance.Updater.emitUpdate(newVdom.props)
  9. }

updateFunctionComponent方法:

  1. function updateFunctionComponent(oldVdom, newVdom) {
  2. let parentDom = findDom(oldVdom).parentNode // 拿到真实dom父节点
  3. let { type, props } = newVdom
  4. let oldRenderVdom = oldVdom.oldRenderVdom // 老的渲染出来的vdom
  5. let newRenderVdom = type(props) // 执行函数得到新的vdom
  6. compareTwoVdom(parentDom, oldRenderVdom, newRenderVdom)
  7. newVdom.oldRenderVdom = newRenderVdom
  8. }

3、修改Component.js文件

classInstance.Updater.emitUpdate(newVdom.props)触发方法:
image.png

修改updateComponent方法:
image.png

添加nextProps参数:
image.png

4、源代码

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