React源码分析(三)

494 阅读7分钟

React源码分析(一)

React源码分析(二)

7.新生命周期

7.1.生命周期图

生命周期图

7.2.getDerivedStateFromProps

  • 是一个静态方法
  • 将父组件传递的props映射到当前组件的state上
  • 状态是合并不是替换

为什么将getDerivedStateFromProps设计为静态方法

getDerivedStateFromProps其实是老生命周期componentWillReceiveProps的替换品,因为componentWillReceiveProps里可以点用setState,很可能会让父组件刷新,父组件一旦刷新,就会重新执行componentWillReceiveProps,这样就容易造成死循环。而getDerivedStateFromProps被设计成了静态方法,里面是不能调用setState的,避免出现死循环。同时getDerivedStateFromProps是单利的比实例属性节约资源。

7.2.1.事例
import React from 'react';
import ReactDOM from 'react-dom';

class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = { num: 0 }
  }

  handlerClick = () => {
    this.setState({ num: this.state.num + 1 });
  }

  render() {
    return (
      <div id={`id-${this.state.num}`}>
        <p>{this.props.name}:{this.state.num}</p>
        <ChildCounter num={this.state.num} />
        <button onClick={this.handlerClick}>加2</button>
      </div>
    )
  }
}

class ChildCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { number: 0 }
  }

  /**
   * componentWillReceiveProps
   * @param {*} nextProps 
   * @param {*} prevState 
   */
  static getDerivedStateFromProps(nextProps, prevState) {
    const { num } = nextProps;
    if (num % 2 === 0) {//当为2的倍数时,更新状态
      return { number: num * 2 };
    } else if (num % 3 === 0) {//当为3的倍数时,更新状态
      return { number: num * 3 };
    } else {
      return null
    }
  }

  render() {
    return <div>{this.state.number}</div>
  }
}

ReactDOM.render(<Counter name='张三' />, document.getElementById('root'));
7.2.2.实现
7.2.2.1.src/react-dom.js
/*
 * @Author: dfh
 * @Date: 2021-02-24 18:34:32
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 09:31:15
 * @Modified By: dfh
 * @FilePath: /day25-react/src/react-dom.js
 */

import { REACT_TEXT } from './constants';
import { addEvent } from './event';

/**
 * 给跟容器挂载的时候
 * @param {*} vdom 需要渲染的虚拟DOM
 * @param {*} container 容器
 */
function render(vdom, container) {
    const dom = createDOM(vdom);
    //挂载真实DOM
    container.appendChild(dom);
    //调用生命周期方法componentDidMount
    dom.componentDidMount && dom.componentDidMount();
}

/**
 * 创建证实DOM
 * @param {*} vdom 虚拟DOM
 */
export function createDOM(vdom) {
    const {
        type,
        props } = vdom;
    //创建真实DOM
    let dom;
    if (type === REACT_TEXT) {//是文本
        dom = document.createTextNode(props.content);
    } else if (typeof type === 'function') {//自定义函数组件
        if (type.isReactComponent) {//类组件
            return mountClassComponent(vdom);
        } else {//函数组件
            return mountFunctionComponent(vdom);
        }
    } else {//原生组件
        dom = document.createElement(type);
    }

    //使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
    updateProps(dom, {}, props);
    if (typeof props.children === 'object' && props.children.type) {//只有一个儿子,并且是虚拟DOM
        render(props.children, dom);//把儿子变成真实DOM,并且挂载到自己身上
    } else if (Array.isArray(props.children)) {//有多个儿子
        reconcileChildren(props.children, dom);
    }

    //将真实DOM挂载到虚拟DOM上,以便后面取
    vdom.dom = dom;
    return dom;
}

/**
 * 把一个类型为自定义类组件的虚拟DOM转化为一个真实DOM并返回
 * @param {*} vdom 类型为自定义类组件的虚拟DOM
 */
function mountClassComponent(vdom) {
    const { type: Clazz, props } = vdom;
    //获取类的实例
    const classInstance = new Clazz(props);
    //让这个类组件的虚拟DOM的classInstance属性指向这个类组件的实例
    vdom.classInstance = classInstance;
    //调用生命周期方法componentWillMount
    if (classInstance.componentWillMount) {
        classInstance.componentWillMount();
    }
+   //执行生命周期方法getDerivedStateFromProps
+   if (Clazz.getDerivedStateFromProps) {
+       const partialState = Clazz.getDerivedStateFromProps(classInstance.props, classInstance.state)
+       if (partialState) {
+           classInstance.state = { ...classInstance.state, ...partialState };
+       }
+   }
    //获取虚拟DOM
    const oldRenderVdom = classInstance.render();
    //将虚拟DOM挂载的组件实例上,以便后面DOM-diff时用
    classInstance.oldRenderVdom = vdom.oldRenderVdom = oldRenderVdom;
    //获取真实DOM
    const dom = createDOM(oldRenderVdom);

    if (classInstance.componentDidMount) {
        dom.componentDidMount = classInstance.componentDidMount;
    }
    //将真实dom挂到实例上上
    classInstance.dom = dom;
    return dom;
}

/**
 * 把一个类型为自定义函数组件的虚拟DOM转换为一个真实DOM并返回
 * @param {*} vdom 类型为自定义函数组件的虚拟DOM
 */
function mountFunctionComponent(vdom) {
    const { type: FunctionComponent, props } = vdom;
    const renderVdom = FunctionComponent(props);
    vdom.oldRenderVdom = renderVdom;
    return createDOM(renderVdom);
}

/**
 * 
 * @param {*} childrenVdom 孩子门的虚拟DOM
 * @param {*} parentDOM 要挂载到的真实DOM
 */
function reconcileChildren(childrenVdom, parentDOM) {
    for (let i = 0; i < childrenVdom.length; i++) {
        const child = childrenVdom[i];
        render(child, parentDOM);//把儿子挂载的自己身上
    }
}
/**
 * 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
 * @param {*} dom 真实DOM
 * @param {*} props 虚拟DOM属性
 */
function updateProps(dom, oldProps, props) {
    for (const key in props) {
        if (key === 'children') continue;//单独处理,不再此处处理
        if (key === 'style') {
            const styleObj = props.style;
            for (const attr in styleObj) {
                dom.style[attr] = styleObj[attr];
            }
        } else if (key.startsWith('on')) {//onClick=>onclick
            // dom[key.toLocaleLowerCase()]=props[key];
            addEvent(dom, key.toLocaleLowerCase(), props[key]);
        } else {//在JS中定义class使用的是className,所以不要改
            dom[key] = props[key];
        }
    }
}

/**
 * 对当前组件进行DOM-DIFF
 * @param {*} parentDOM 老得父真实DOM
 * @param {*} oldRenderVdom 老得虚拟DOM
 * @param {*} newRenderVdom 新的虚拟DOM
 * @param {*} nextDom 下一个真实DOM,主要用来插入找位置用
 */
export function compareTwoVdom(parentDOM, oldRenderVdom, newRenderVdom, nextDom) {
    if (!oldRenderVdom && !newRenderVdom) {//新老虚拟DOM都为null
        return null;
    } else if (oldRenderVdom && !newRenderVdom) {//新的虚拟DOM为NULL,老得存在
        const currentDOM = findDOM(oldRenderVdom);//找到此虚拟DOM对应的真实DOM
        currentDOM && parentDOM.removeChild(currentDOM);//移除此老得真实DOM
        //调用生命周期方法
        oldRenderVdom.classInstance && oldRenderVdom.classInstance.componentWillUnmount && oldRenderVdom.classInstance.componentWillUnmount()
    } else if (!oldRenderVdom && newRenderVdom) {//新的虚拟DOM存在,老得虚拟DOM为NULL
        const newDOM = createDOM(newRenderVdom);//获取真实DOM
        if (nextDom) {
            parentDOM.insertBefore(newDOM, nextDom);
        } else {
            parentDOM.appendChild(newDOM);
        }
    } else if (oldRenderVdom && newRenderVdom && oldRenderVdom.type !== newRenderVdom.type) {//新老虚拟DOM都存在,但是类型不同
        const oldDOM = findDOM(oldRenderVdom);//老得真实DOM
        const newDOM = createDOM(newRenderVdom);//新的真实DOM
        parentDOM.replaceChild(newDOM, oldDOM);
        //调用生命周期方法
        oldRenderVdom.classInstance && oldRenderVdom.classInstance.componentWillUnmount && oldRenderVdom.classInstance.componentWillUnmount()
    } else {//新老都有,类型也一样,要进行深度DOM-DIFF
        updateElement(oldRenderVdom, newRenderVdom);
    }
}

/**
 * 深度对比两个虚拟DOM
 * @param {*} oldRenderVdom 老得虚拟DOM
 * @param {*} newRenderVdom 新的虚拟DOM
 */
function updateElement(oldRenderVdom, newRenderVdom) {
    if (oldRenderVdom.type === REACT_TEXT) {//文本
        const currentDOM = newRenderVdom.dom = oldRenderVdom.dom;//复用老得真实DOM节点
        currentDOM.textContent = newRenderVdom.props.content;//直接修改老的DOM节点的文件就可以了
    } else if (typeof oldRenderVdom.type === 'string') {//说明是一个原生组件
        const currentDOM = newRenderVdom.dom = oldRenderVdom.dom;//复用老得真实DOM
        //先更新属性
        updateProps(currentDOM, oldRenderVdom.props, newRenderVdom.props);
        //比较儿子们
        updateChildren(currentDOM, oldRenderVdom.props.children, newRenderVdom.props.children);
    } else if (typeof oldRenderVdom.type === 'function') {
        if (oldRenderVdom.type.isReactComponent) {
            updateClassComponent(oldRenderVdom, newRenderVdom);//老新都是类组件,进行类组件更新
        } else {
            updateFunctionComponent(oldRenderVdom, newRenderVdom);//新老都是函数组件,进行函数组件更新
        }
    }
}

/**
 *  如果老得虚拟DOM节点和新的虚拟DOM节点都是函数的话,走这个更新逻辑
 * @param {*} oldVdom 老得虚拟DOM
 * @param {*} newVdom 新的虚拟DOM
 */
function updateFunctionComponent(oldVdom, newVdom) {
    const parentDOM = findDOM(oldVdom).parentNode;//找到老得父节点
    const { type: FunctionComponent, props } = newVdom;
    const oldRenderVdom = oldVdom.oldRenderVdom;//老得的渲染虚拟DOM
    const newRenderVdom = FunctionComponent(props);//新的渲染虚拟DOM
    compareTwoVdom(parentDOM, oldRenderVdom, newRenderVdom);//比较虚拟DOM
    newVdom.oldRenderVdom = newRenderVdom;
}

/**
 * 如果老得虚拟DOM节点和新的虚拟DOM节点都是类组件的话,走这个更新逻辑
 * @param {*} oldVdom 老得虚拟DOM
 * @param {*} newVdom 新的虚拟DOM
 */
function updateClassComponent(oldVdom, newVdom) {
    const classInstance = newVdom.classInstance = oldVdom.classInstance;//复用老得类的实例
    newVdom.oldRenderVdom = oldVdom.oldRenderVdom;//上一次类组件的渲染出来的虚拟DOM
    if (classInstance.componentWillReceiveProps) {//组件将要接受到新的属性
        classInstance.componentWillReceiveProps();
    }
    //触发组件的更新,把新的属性传递过去
    classInstance.updater.emitUpdate(newVdom.props);
}
/**
 * 深度比较孩子们
 * @param {*} parentDOM 父DOM 
 * @param {*} oldChildren 老得儿子们
 * @param {*} newChildren 新的儿子们
 */
function updateChildren(parentDOM, oldChildren, newChildren) {
    //孩子可能是数组或者对象(单节点是对象)
    oldChildren = Array.isArray(oldChildren) ? oldChildren : [oldChildren];
    newChildren = Array.isArray(newChildren) ? newChildren : [newChildren];
    //获取最大的长度
    const maxLen = Math.max(oldChildren.length, newChildren.length);
    for (let i = 0; i < maxLen; i++) {
        //在儿子们里查找,找到索引大于当前索引的
        const nextDOM = oldChildren.find((item, index) => index > i && item && item.dom)
        //递归比较孩子
        compareTwoVdom(parentDOM, oldChildren[i], newChildren[i], nextDOM && nextDOM.dom);
    }
}


/**
 * 查找此虚拟DOM对象的真实DOM
 * @param {*} vdom 
 */
export function findDOM(vdom) {
    const { type } = vdom;
    let dom;
    if (typeof type === 'function') {
        dom = findDOM(vdom.oldRenderVdom)
    } else {
        dom = vdom.dom;
    }
    return dom
}

const ReactDOM = {
    render
}

export default ReactDOM;
7.2.2.2.src/Component.js
/*
 * @Author: dfh
 * @Date: 2021-02-24 23:34:42
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 09:21:37
 * @Modified By: dfh
 * @FilePath: /day25-react/src/Component.js
 */
import { compareTwoVdom, findDOM } from './react-dom'

//更新队列
export let updateQueue = {
    isBatchingUpdate: false,//当前是否处于批量更新模式
    updaters: [],
    batchUpdate() {//批量更新
        for (let updater of this.updaters) {
+           updater.updateComponent();
        }
        this.isBatchingUpdate = false;
        this.updaters.length = 0;
    }
}
//更新器
class Updater {
    constructor(classInstance) {
        this.classInstance = classInstance;//类组件的实例
        this.pendingStates = [];//等待生效的状态,可能是一个对象,也可能是一个函数
        this.cbs = [];//存放回调
    }

    /**
     * 
     * @param {*} partialState 等待更新生效的状态
     * @param {*} cb 状态更新的回调
     */
    addState(partialState, cb) {
        this.pendingStates.push(partialState);
        typeof cb === 'function' && this.cbs.push(cb);
        this.emitUpdate();
    }

    //一个组件不管属性变了,还是状态变了,都会更新
    emitUpdate(nextProps) {
        this.nextProps = nextProps;//缓存起来
        if (updateQueue.isBatchingUpdate) {//当前处于批量更新模式,先缓存updater
            updateQueue.updaters.push(this);//本次setState调用结束
        } else {//当前处于非批量更新模式,执行更新
+           this.updateComponent();//直接更新组件
        }
    }

+   updateComponent() {
        const { classInstance, pendingStates, cbs, nextProps } = this;
        if (nextProps || pendingStates.length > 0) {//有setState
+           shouldUpdate(classInstance, nextProps, this.getState(nextProps))
        }
    }

+   getState(nextProps) {//计算新状态
        const { classInstance, pendingStates } = this;
        let { state } = classInstance;//获取老状态
        pendingStates.forEach(newState => {
            //newState可能是对象,也可能是函数,对象setState的两种方式
            if (typeof newState === 'function') {
                newState = newState(state);
            }
            state = { ...state, ...newState };
        })
        pendingStates.length = 0;//清空数组
+       //执行生命周期方法
+       if (classInstance.constructor.getDerivedStateFromProps) {
+           const partialState = classInstance.constructor.getDerivedStateFromProps(nextProps, classInstance.state)
+           if (partialState) {//状态合并
+               state = { ...state, ...partialState };
+           }
+       }
        return state;
    }
}

/**
 * 判断组件是否需要更新
 * @param {*} classInstance 类组件实例
 * @param {*} nextProps 新的props
 * @param {*} newState 新状态
 */
function shouldUpdate(classInstance, nextProps, newState) {
    let willUpdate = true;//是否需要更新
    //如果有这个方法,并且这个方法的返回值为false,则不需要继续向下更新了,否则就更新
    if (classInstance.shouldComponentUpdate && !classInstance.shouldComponentUpdate(nextProps, newState)) {
        willUpdate = false;
    }
    //如果需要更新,并且组件调用类componentWillUpdate方法
    if (willUpdate && classInstance.componentWillUpdate) {
        classInstance.componentWillUpdate();//执行生命周期方法componentWillUpdate
    }
    //不管是否需要更新,属性和状态都有改变
    if (nextProps) {
        classInstance.props = nextProps;
    }

    //不管组件要不要更新,组件的state一定会改变
    classInstance.state = newState;
    //如果需要更新,走组件的更新逻辑
+   willUpdate && classInstance.updateComponent()
}
class Component {
    //用来判断是类组件
    static isReactComponent = true;
    constructor(props) {
        this.props = props;
        this.state = {};
        //每个类组件都有一个更新器
        this.updater = new Updater(this);
    }

    setState(partialState, cb) {
        this.updater.addState(partialState, cb);
    }

    /**
     * 强制更新:一般来说组件的属性或者状态没有改变时组件时不更新的,如果我们还想更新组件可以调用此方法更新组件
     */
+   forceUpdate() {
+       let nextState = this.state;
+       let nextProps = this.props;
+       if (this.constructor.getDerivedStateFromProps) {
+           const partialState = this.constructor.getDerivedStateFromProps(nextProps, nextState);
+           if(partialState){
+               nextState={...nextState,partialState}
+           }
+       }
+       this.state=nextState;
+       this.updateComponent();
+   }

    /**
     * 更新组件
     */
+   updateComponent() {
        const newRenderVdom = this.render();//新的虚拟DOM
        const oldRenderVdom = this.oldRenderVdom;//老得虚拟DOM
        const dom = findDOM(oldRenderVdom);//老得真实DOM
        compareTwoVdom(dom.parentNode, oldRenderVdom, newRenderVdom);
        this.oldRenderVdom = newRenderVdom;//比较完毕后,重新赋值老的虚拟节点
        //调用生命周期方法componentDidUpdate
        this.componentDidUpdate && this.componentDidUpdate();
    }
}

export default Component;

7.3.getSnapshotBeforeUpdate

getSnapshotBeforeUpdate被调用与render之后,可以读取但是无法使用DOM的时候,它可以在组件可能更改之前从DOM捕获一些信息(例如:滚动位置...),此生命周期返回的任何值都将作为参数传递给componentDidUpdate

7.3.1.事例
/*
 * @Author: dfh
 * @Date: 2021-02-24 18:18:22
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 11:34:06
 * @Modified By: dfh
 * @FilePath: /day25-react/src/index.js
 */
import React from 'react';
import ReactDOM from 'react-dom';

class Counter extends React.Component {
  ulRef = React.createRef();//{current:null}
  constructor(props) {
    super(props)
    this.state = { list: [] }
  }

  getSnapshotBeforeUpdate() {
    return this.ulRef.current.scrollHeight;
  }

  /**
   * 
   * @param {*} prevProps 老得props
   * @param {*} prevState 老得state
   * @param {*} scrollHeight getSnapshotBeforeUpdate传递的值
   */
  componentDidUpdate(prevProps, prevState, scrollHeight) {
    console.log('本次新增的高度:', this.ulRef.current.scrollHeight - scrollHeight)
  }

  handlerClick = () => {
    const list = this.state.list;
    list.push(list.length);
    this.setState({ list });
  }

  render() {
    return (
      <div id={`id-${this.state.num}`}>
        <button onClick={this.handlerClick}>+</button>
        <ul ref={this.ulRef}>
          {this.state.list.map((item, index) => <li key={index}>{index}</li>)}
        </ul>
      </div>
    )
  }
}


ReactDOM.render(<Counter name='张三' />, document.getElementById('root'));
7.3.2.实现
7.3.2.1.src/react.js
/*
 * @Author: dfh
 * @Date: 2021-02-24 18:34:24
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 11:30:06
 * @Modified By: dfh
 * @FilePath: /day25-react/src/react.js
 */
import Component from './Component';
import { wrapToVdom } from './utils';
/**
 * 
 * @param {*} type 元素类型
 * @param {*} config 配置对象
 * @param {*} children 孩子或者孩子门
 */
function createElement(type, config, children) {
+   let ref, key;
    if (config) {
        delete config.__source;
        delete config.__self;
+       ref = config.ref;
+       key = config.key;
+       delete config.ref;
+       delete config.key;
    }
    let props = { ...config };

    if (arguments.length > 3) {//children是一个数组
        props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
    } else {
        props.children = wrapToVdom(children);
    }
    return {
        type,
        props,
+       ref,
+       key
    }
}

+ function createRef() {
+   return { current: null }
+ }
const React = {
    createElement,
    Component,
+   createRef
}
export default React;
7.3.2.2.src/react-dom.js
/*
 * @Author: dfh
 * @Date: 2021-02-24 18:34:32
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 11:32:14
 * @Modified By: dfh
 * @FilePath: /day25-react/src/react-dom.js
 */

import { REACT_TEXT } from './constants';
import { addEvent } from './event';

/**
 * 给跟容器挂载的时候
 * @param {*} vdom 需要渲染的虚拟DOM
 * @param {*} container 容器
 */
function render(vdom, container) {
    const dom = createDOM(vdom);
    //挂载真实DOM
    container.appendChild(dom);
    //调用生命周期方法componentDidMount
    dom.componentDidMount && dom.componentDidMount();
}

/**
 * 创建证实DOM
 * @param {*} vdom 虚拟DOM
 */
export function createDOM(vdom) {
    const {
        type,
        props,
+       key,
+       ref
    } = vdom;
    //创建真实DOM
    let dom;
    if (type === REACT_TEXT) {//是文本
        dom = document.createTextNode(props.content);
    } else if (typeof type === 'function') {//自定义函数组件
        if (type.isReactComponent) {//类组件
            return mountClassComponent(vdom);
        } else {//函数组件
            return mountFunctionComponent(vdom);
        }
    } else {//原生组件
        dom = document.createElement(type);
    }

    //使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
    updateProps(dom, {}, props);
    if (typeof props.children === 'object' && props.children.type) {//只有一个儿子,并且是虚拟DOM
        render(props.children, dom);//把儿子变成真实DOM,并且挂载到自己身上
    } else if (Array.isArray(props.children)) {//有多个儿子
        reconcileChildren(props.children, dom);
    }

    //将真实DOM挂载到虚拟DOM上,以便后面取
    vdom.dom = dom;
    //通过虚拟DOM创建真实DOM之后,虚拟DOM的ref属性的current属性等于真实DOM
+   ref && (ref.current = dom);
    return dom;
}

/**
 * 把一个类型为自定义类组件的虚拟DOM转化为一个真实DOM并返回
 * @param {*} vdom 类型为自定义类组件的虚拟DOM
 */
function mountClassComponent(vdom) {
    const { type: Clazz, props } = vdom;
    //获取类的实例
    const classInstance = new Clazz(props);
    //让这个类组件的虚拟DOM的classInstance属性指向这个类组件的实例
    vdom.classInstance = classInstance;
    //调用生命周期方法componentWillMount
    if (classInstance.componentWillMount) {
        classInstance.componentWillMount();
    }
    //执行生命周期方法getDerivedStateFromProps
    if (Clazz.getDerivedStateFromProps) {
        const partialState = Clazz.getDerivedStateFromProps(classInstance.props, classInstance.state)
        if (partialState) {
            classInstance.state = { ...classInstance.state, ...partialState };
        }
    }
    //获取虚拟DOM
    const oldRenderVdom = classInstance.render();
    //将虚拟DOM挂载的组件实例上,以便后面DOM-diff时用
    classInstance.oldRenderVdom = vdom.oldRenderVdom = oldRenderVdom;
    //获取真实DOM
    const dom = createDOM(oldRenderVdom);

    if (classInstance.componentDidMount) {
        dom.componentDidMount = classInstance.componentDidMount;
    }
    //将真实dom挂到实例上上
    classInstance.dom = dom;

    return dom;
}

/**
 * 把一个类型为自定义函数组件的虚拟DOM转换为一个真实DOM并返回
 * @param {*} vdom 类型为自定义函数组件的虚拟DOM
 */
function mountFunctionComponent(vdom) {
    const { type: FunctionComponent, props } = vdom;
    const renderVdom = FunctionComponent(props);
    vdom.oldRenderVdom = renderVdom;
    return createDOM(renderVdom);
}

/**
 * 
 * @param {*} childrenVdom 孩子门的虚拟DOM
 * @param {*} parentDOM 要挂载到的真实DOM
 */
function reconcileChildren(childrenVdom, parentDOM) {
    for (let i = 0; i < childrenVdom.length; i++) {
        const child = childrenVdom[i];
        render(child, parentDOM);//把儿子挂载的自己身上
    }
}
/**
 * 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
 * @param {*} dom 真实DOM
 * @param {*} props 虚拟DOM属性
 */
function updateProps(dom, oldProps, props) {
    for (const key in props) {
        if (key === 'children') continue;//单独处理,不再此处处理
        if (key === 'style') {
            const styleObj = props.style;
            for (const attr in styleObj) {
                dom.style[attr] = styleObj[attr];
            }
        } else if (key.startsWith('on')) {//onClick=>onclick
            // dom[key.toLocaleLowerCase()]=props[key];
            addEvent(dom, key.toLocaleLowerCase(), props[key]);
        } else {//在JS中定义class使用的是className,所以不要改
            dom[key] = props[key];
        }
    }
}

/**
 * 对当前组件进行DOM-DIFF
 * @param {*} parentDOM 老得父真实DOM
 * @param {*} oldRenderVdom 老得虚拟DOM
 * @param {*} newRenderVdom 新的虚拟DOM
 * @param {*} nextDom 下一个真实DOM,主要用来插入找位置用
 */
export function compareTwoVdom(parentDOM, oldRenderVdom, newRenderVdom, nextDom) {
    if (!oldRenderVdom && !newRenderVdom) {//新老虚拟DOM都为null
        return null;
    } else if (oldRenderVdom && !newRenderVdom) {//新的虚拟DOM为NULL,老得存在
        const currentDOM = findDOM(oldRenderVdom);//找到此虚拟DOM对应的真实DOM
        currentDOM && parentDOM.removeChild(currentDOM);//移除此老得真实DOM
        //调用生命周期方法
        oldRenderVdom.classInstance && oldRenderVdom.classInstance.componentWillUnmount && oldRenderVdom.classInstance.componentWillUnmount()
    } else if (!oldRenderVdom && newRenderVdom) {//新的虚拟DOM存在,老得虚拟DOM为NULL
        const newDOM = createDOM(newRenderVdom);//获取真实DOM
        if (nextDom) {
            parentDOM.insertBefore(newDOM, nextDom);
        } else {
            parentDOM.appendChild(newDOM);
        }
    } else if (oldRenderVdom && newRenderVdom && oldRenderVdom.type !== newRenderVdom.type) {//新老虚拟DOM都存在,但是类型不同
        const oldDOM = findDOM(oldRenderVdom);//老得真实DOM
        const newDOM = createDOM(newRenderVdom);//新的真实DOM
        parentDOM.replaceChild(newDOM, oldDOM);
        //调用生命周期方法
        oldRenderVdom.classInstance && oldRenderVdom.classInstance.componentWillUnmount && oldRenderVdom.classInstance.componentWillUnmount()
    } else {//新老都有,类型也一样,要进行深度DOM-DIFF
        updateElement(oldRenderVdom, newRenderVdom);
    }
}

/**
 * 深度对比两个虚拟DOM
 * @param {*} oldRenderVdom 老得虚拟DOM
 * @param {*} newRenderVdom 新的虚拟DOM
 */
function updateElement(oldRenderVdom, newRenderVdom) {
    if (oldRenderVdom.type === REACT_TEXT) {//文本
        const currentDOM = newRenderVdom.dom = oldRenderVdom.dom;//复用老得真实DOM节点
        currentDOM.textContent = newRenderVdom.props.content;//直接修改老的DOM节点的文件就可以了
    } else if (typeof oldRenderVdom.type === 'string') {//说明是一个原生组件
        const currentDOM = newRenderVdom.dom = oldRenderVdom.dom;//复用老得真实DOM
        //先更新属性
        updateProps(currentDOM, oldRenderVdom.props, newRenderVdom.props);
        //比较儿子们
        updateChildren(currentDOM, oldRenderVdom.props.children, newRenderVdom.props.children);
    } else if (typeof oldRenderVdom.type === 'function') {
        if (oldRenderVdom.type.isReactComponent) {
            updateClassComponent(oldRenderVdom, newRenderVdom);//老新都是类组件,进行类组件更新
        } else {
            updateFunctionComponent(oldRenderVdom, newRenderVdom);//新老都是函数组件,进行函数组件更新
        }
    }
}

/**
 *  如果老得虚拟DOM节点和新的虚拟DOM节点都是函数的话,走这个更新逻辑
 * @param {*} oldVdom 老得虚拟DOM
 * @param {*} newVdom 新的虚拟DOM
 */
function updateFunctionComponent(oldVdom, newVdom) {
    const parentDOM = findDOM(oldVdom).parentNode;//找到老得父节点
    const { type: FunctionComponent, props } = newVdom;
    const oldRenderVdom = oldVdom.oldRenderVdom;//老得的渲染虚拟DOM
    const newRenderVdom = FunctionComponent(props);//新的渲染虚拟DOM
    compareTwoVdom(parentDOM, oldRenderVdom, newRenderVdom);//比较虚拟DOM
    newVdom.oldRenderVdom = newRenderVdom;
}

/**
 * 如果老得虚拟DOM节点和新的虚拟DOM节点都是类组件的话,走这个更新逻辑
 * @param {*} oldVdom 老得虚拟DOM
 * @param {*} newVdom 新的虚拟DOM
 */
function updateClassComponent(oldVdom, newVdom) {
    const classInstance = newVdom.classInstance = oldVdom.classInstance;//复用老得类的实例
    newVdom.oldRenderVdom = oldVdom.oldRenderVdom;//上一次类组件的渲染出来的虚拟DOM
    if (classInstance.componentWillReceiveProps) {//组件将要接受到新的属性
        classInstance.componentWillReceiveProps();
    }
    //触发组件的更新,把新的属性传递过去
    classInstance.updater.emitUpdate(newVdom.props);
}
/**
 * 深度比较孩子们
 * @param {*} parentDOM 父DOM 
 * @param {*} oldChildren 老得儿子们
 * @param {*} newChildren 新的儿子们
 */
function updateChildren(parentDOM, oldChildren, newChildren) {
    //孩子可能是数组或者对象(单节点是对象)
    oldChildren = Array.isArray(oldChildren) ? oldChildren : [oldChildren];
    newChildren = Array.isArray(newChildren) ? newChildren : [newChildren];
    //获取最大的长度
    const maxLen = Math.max(oldChildren.length, newChildren.length);
    for (let i = 0; i < maxLen; i++) {
        //在儿子们里查找,找到索引大于当前索引的
        const nextDOM = oldChildren.find((item, index) => index > i && item && item.dom)
        //递归比较孩子
        compareTwoVdom(parentDOM, oldChildren[i], newChildren[i], nextDOM && nextDOM.dom);
    }
}


/**
 * 查找此虚拟DOM对象的真实DOM
 * @param {*} vdom 
 */
export function findDOM(vdom) {
    const { type } = vdom;
    let dom;
    if (typeof type === 'function') {
        dom = findDOM(vdom.oldRenderVdom)
    } else {
        dom = vdom.dom;
    }
    return dom
}

const ReactDOM = {
    render
}

export default ReactDOM;
7.3.2.3.src/Component.js
/*
 * @Author: dfh
 * @Date: 2021-02-24 23:34:42
 * @LastEditors: dfh
 * @LastEditTime: 2021-03-01 11:34:22
 * @Modified By: dfh
 * @FilePath: /day25-react/src/Component.js
 */
import { compareTwoVdom, findDOM } from './react-dom'

//更新队列
export let updateQueue = {
    isBatchingUpdate: false,//当前是否处于批量更新模式
    updaters: [],
    batchUpdate() {//批量更新
        for (let updater of this.updaters) {
            updater.updateComponent();
        }
        this.isBatchingUpdate = false;
        this.updaters.length = 0;
    }
}
//更新器
class Updater {
    constructor(classInstance) {
        this.classInstance = classInstance;//类组件的实例
        this.pendingStates = [];//等待生效的状态,可能是一个对象,也可能是一个函数
        this.cbs = [];//存放回调
    }

    /**
     * 
     * @param {*} partialState 等待更新生效的状态
     * @param {*} cb 状态更新的回调
     */
    addState(partialState, cb) {
        this.pendingStates.push(partialState);
        typeof cb === 'function' && this.cbs.push(cb);
        this.emitUpdate();
    }

    //一个组件不管属性变了,还是状态变了,都会更新
    emitUpdate(nextProps) {
        this.nextProps = nextProps;//缓存起来
        if (updateQueue.isBatchingUpdate) {//当前处于批量更新模式,先缓存updater
            updateQueue.updaters.push(this);//本次setState调用结束
        } else {//当前处于非批量更新模式,执行更新
            this.updateComponent();//直接更新组件
        }
    }

    updateComponent() {
        const { classInstance, pendingStates, cbs, nextProps } = this;
        if (nextProps || pendingStates.length > 0) {//有setState
            shouldUpdate(classInstance, nextProps, this.getState(nextProps))
        }
    }

    getState(nextProps) {//计算新状态
        const { classInstance, pendingStates } = this;
        let { state } = classInstance;//获取老状态
        pendingStates.forEach(newState => {
            //newState可能是对象,也可能是函数,对象setState的两种方式
            if (typeof newState === 'function') {
                newState = newState(state);
            }
            state = { ...state, ...newState };
        })
        pendingStates.length = 0;//清空数组
        if (classInstance.constructor.getDerivedStateFromProps) {
            const partialState = classInstance.constructor.getDerivedStateFromProps(nextProps, classInstance.state)
            if (partialState) {//状态合并
                state = { ...state, ...partialState };
            }
        }
        return state;
    }
}

/**
 * 判断组件是否需要更新
 * @param {*} classInstance 类组件实例
 * @param {*} nextProps 新的props
 * @param {*} newState 新状态
 */
function shouldUpdate(classInstance, nextProps, newState) {
    let willUpdate = true;//是否需要更新
    //如果有这个方法,并且这个方法的返回值为false,则不需要继续向下更新了,否则就更新
    if (classInstance.shouldComponentUpdate && !classInstance.shouldComponentUpdate(nextProps, newState)) {
        willUpdate = false;
    }
    //如果需要更新,并且组件调用类componentWillUpdate方法
    if (willUpdate && classInstance.componentWillUpdate) {
        classInstance.componentWillUpdate();//执行生命周期方法componentWillUpdate
    }
    //不管是否需要更新,属性和状态都有改变
    if (nextProps) {
        classInstance.props = nextProps;
    }

    //不管组件要不要更新,组件的state一定会改变
    classInstance.state = newState;
    //如果需要更新,走组件的更新逻辑
    willUpdate && classInstance.updateComponent()
}
class Component {
    //用来判断是类组件
    static isReactComponent = true;
    constructor(props) {
        this.props = props;
        this.state = {};
        //每个类组件都有一个更新器
        this.updater = new Updater(this);
    }

    setState(partialState, cb) {
        this.updater.addState(partialState, cb);
    }

    /**
     * 强制更新:一般来说组件的属性或者状态没有改变时组件时不更新的,如果我们还想更新组件可以调用此方法更新组件
     */
    forceUpdate() {
        let nextState = this.state;
        let nextProps = this.props;
        if (this.constructor.getDerivedStateFromProps) {
            const partialState = this.constructor.getDerivedStateFromProps(nextProps, nextState);
            if (partialState) {
                nextState = { ...nextState, partialState }
            }
        }
        this.state = nextState;
        this.updateComponent();
    }

    /**
     * 更新组件
     */
    updateComponent() {
        const newRenderVdom = this.render();//新的虚拟DOM
        const oldRenderVdom = this.oldRenderVdom;//老得虚拟DOM
        const dom = findDOM(oldRenderVdom);//老得真实DOM
+       const extraArgs = this.getSnapshotBeforeUpdate && this.getSnapshotBeforeUpdate();
        compareTwoVdom(dom.parentNode, oldRenderVdom, newRenderVdom);
        this.oldRenderVdom = newRenderVdom;//比较完毕后,重新赋值老的虚拟节点
        //调用生命周期方法componentDidUpdate
+       this.componentDidUpdate && this.componentDidUpdate(this.props, this.state, extraArgs);
    }
}

export default Component;