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;