Open buxuku opened 3 years ago
在类组件中,shouldComponentUpdate默认返回的是true,React为我们提供了一个PureComponent的父类,它会自动帮我们对props和state进行浅对比,如果值一样,则返回false以来优化render.
shouldComponentUpdate
true
PureComponent
props
state
false
render
PureComponent的实现,其实就是实现shouldComponentUpdate这个方法.
在src/react/index.js里添加一个PureComponent的父类
src/react/index.js
-import {wrapToVdom, flatten} from '../utils'; +import {wrapToVdom, flatten, shallowEqual} from '../utils'; +class PureComponnent extends Component{ + shouldComponentUpdate(nextProps, nextState){ + return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState); + } +} const React = { createElement, Component, + PureComponnent, createRef, createContext, forwardRef, }
在src/utils/index.js里面实现shallowEqual这个方法
src/utils/index.js
shallowEqual
/** * 浅比较对比两个对象 * @param obj1 * @param obj2 * @returns {boolean} */ export function shallowEqual(obj1, obj2){ debugger if(obj1 === obj2) return true; if(typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null){ return false; } const obj1Keys = Object.keys(obj1); const obj2Keys = Object.keys(obj2); if(obj1Keys.length !== obj2Keys.length){ return false; } for(let key of obj1Keys){ if(!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]){ return false; } } return true; }
新建src/components/PureComponent.js, 这个组件点+1会更新state,点nothing不会更新state,这个时候可以看到只有state值产生变化了之后才会进行render操作.
src/components/PureComponent.js
+1
nothing
import React from '../react'; class PureComponnent extends React.PureComponnent { constructor(props) { super(props); this.state = { count: 1, } } handleAdd = (num = 1) => { this.setState({count: this.state.count + num}) } render() { console.log('PureComponnent render'); return ( <div> <p>count: {this.state.count}</p> <button onClick={() => this.handleAdd(1)}>+1</button> <button onClick={() => this.handleAdd(0)}>nothing</button> </div> ) } } export default PureComponnent;
而对于函数组件, React提供了一个React.memo的方法,被React.memo包裹了的函数组件,会自动对props属性进行浅对比,有更新,才重新渲染该组件.
React.memo
和createContext一样,React.memo返回的也是一个虚拟Dom的对象.
createContext
其中有一个$$typeof的标识,compare表示传入的自定义的对比方法,type指向的是函数组件本身.
$$typeof
compare
type
在src/constant/index.js新增一个memo的类型
src/constant/index.js
memo
+export const REACT_MEMO = Symbol('REACT_MEMO');
在src/react/index.js实现memo这个方法
-import {REACT_FORWARD_COMPONENT, REACT_CONTEXT, REACT_PROVIDER} from "../constants"; +import {REACT_FORWARD_COMPONENT, REACT_CONTEXT, REACT_PROVIDER, REACT_MEMO} from "../constants"; +function memo(type, compare = shallowEqual){ + return { + $$typeof: REACT_MEMO, + compare, + type, + } +} const React = { createElement, Component, PureComponnent, + memo, createRef, createContext, forwardRef, }
和createContext一样,在src/react-dom/index.js增加mountMemoComponent和updateMemoComponent方法.需要注意的是,函数组件每次都是独立执行的,所以我们需要在虚拟Dom上面挂载一个prevProps的属性来记录props以方便更新的时候对props对比对比操作.
src/react-dom/index.js
mountMemoComponent
updateMemoComponent
prevProps
-import {REACT_FORWARD_COMPONENT, REACT_TEXT, REACT_CONTEXT, REACT_PROVIDER, MOVE, REMOVE, INSERT} from '../constants'; +import {REACT_FORWARD_COMPONENT, REACT_TEXT, REACT_CONTEXT, REACT_PROVIDER, MOVE, REMOVE, INSERT, REACT_MEMO} from '../constants'; function createDom(vdom) { if (isNotNeedRender(vdom)) return let dom; const {type, props, ref} = vdom; + if (type && type.$$typeof === REACT_MEMO) { + return mountMemoComponent(vdom); + } if (type && type.$$typeof === REACT_FORWARD_COMPONENT) { return mountForwardComponent(vdom); } if (type === REACT_TEXT) { dom = document.createTextNode(props.content); } if (type && type.$$typeof === REACT_PROVIDER){ return mountProviderComponent(vdom); } if (type && type.$$typeof === REACT_CONTEXT){ return mountContextComponent(vdom); } if (typeof type === 'string') { dom = document.createElement(type); renderAttributes(dom, props); } if (typeof type === 'function') { if (type.isReactComponent) { // 是一个类组件 return mountClassComponent(vdom); } let renderVdom = wrapToVdom(type(props)); vdom.oldVdom = renderVdom; // 让type执行,返回虚拟DOM,继续处理返回的虚拟DOM return createDom(renderVdom); } if (props) { const {children} = props; if (Array.isArray(children)) { reconcileChildren(children, dom); } else { reconcileChildren([children], dom); } } vdom.dom = dom; if (ref) { ref.current = dom; } return dom; } +function mountMemoComponent(vdom){ + const {type, props} = vdom; + const renderVdom = type.type(props); + vdom.oldVdom = renderVdom; + vdom.prevProps = props; // 缓存当前props用于更新时做对比. + return createDom(renderVdom); +} function updateElement(oldVdom, newVdom) { if (oldVdom.type === REACT_TEXT && newVdom.type === REACT_TEXT ) { const dom = newVdom.dom = findDom(oldVdom); if(oldVdom.props.content !== newVdom.props.content){ // 当文本内容有变化才进行更新 dom.textContent = newVdom.props.content; } } + if(oldVdom.type && oldVdom.type.$$typeof === REACT_MEMO){ + updateMemoComponent(oldVdom, newVdom); + } if (oldVdom.type && oldVdom.type.$$typeof === REACT_PROVIDER){ updateProviderComponent(oldVdom, newVdom); } if (oldVdom.type && oldVdom.type.$$typeof === REACT_CONTEXT){ updateConsumerComponent(oldVdom, newVdom); } if (typeof oldVdom.type === 'string') { // 原生的HTML元素 const currentDom = newVdom.dom = findDom(oldVdom); // 把老的Dom节点直接复制过来 renderAttributes(currentDom, newVdom.props, oldVdom.props); // 更新节点属性 updateChildren(currentDom, oldVdom.props.children, newVdom.props.children); } else if(typeof oldVdom.type === 'function'){ if (oldVdom.type.isReactComponent) { updateClassComponent(oldVdom, newVdom) }else{ updateFunctionComponent(oldVdom, newVdom); } } } +function updateMemoComponent(oldVdom, newVdom){ + const {type, props} = newVdom; + if(!type.compare(oldVdom.prevProps, props)){ + const renderVdom = type.type(props); + const parentDom = findDom(oldVdom); + compareTwoVdoms(oldVdom.oldVdom, renderVdom, parentDom); + newVdom.oldVdom = renderVdom; + } else { + newVdom.oldVdom = oldVdom.oldVdom + } + newVdom.prevProps = props; }
修改前面那个PureComponennt组件,增加一个函数组件
PureComponennt
import React from '../react'; function Child(props) { console.log('Child render'); return <div> Child: {props.name} </div> } const ChildMemo = React.memo(Child); class PureComponnent extends React.PureComponnent { constructor(props) { super(props); this.state = { count: 1, name: 'hello', } } handleAdd = (num = 1) => { this.setState({count: this.state.count + num}) } changeName = () => { this.setState({ name: this.state.name + '.' }) } render() { console.log('PureComponnent render'); return ( <div> <p>count: {this.state.count}</p> <ChildMemo name={this.state.name}/> <button onClick={() => this.handleAdd(1)}>+1</button> <button onClick={() => this.handleAdd(0)}>nothing</button> <button onClick={this.changeName}>nothing</button> </div> ) } } export default PureComponnent;
现在可以发现,当只修改count值的时候,将不会触发Child组件的render.
count
Child
在类组件中,
shouldComponentUpdate
默认返回的是true
,React为我们提供了一个PureComponent
的父类,它会自动帮我们对props
和state
进行浅对比,如果值一样,则返回false
以来优化render
.PureComponent
的实现,其实就是实现shouldComponentUpdate
这个方法.在
src/react/index.js
里添加一个PureComponent
的父类在
src/utils/index.js
里面实现shallowEqual
这个方法新建
src/components/PureComponent.js
, 这个组件点+1
会更新state
,点nothing
不会更新state
,这个时候可以看到只有state
值产生变化了之后才会进行render
操作.而对于函数组件, React提供了一个
React.memo
的方法,被React.memo
包裹了的函数组件,会自动对props
属性进行浅对比,有更新,才重新渲染该组件.和
createContext
一样,React.memo
返回的也是一个虚拟Dom的对象.其中有一个
$$typeof
的标识,compare
表示传入的自定义的对比方法,type
指向的是函数组件本身.在
src/constant/index.js
新增一个memo
的类型在
src/react/index.js
实现memo
这个方法和
createContext
一样,在src/react-dom/index.js
增加mountMemoComponent
和updateMemoComponent
方法.需要注意的是,函数组件每次都是独立执行的,所以我们需要在虚拟Dom上面挂载一个prevProps
的属性来记录props
以方便更新的时候对props
对比对比操作.修改前面那个
PureComponennt
组件,增加一个函数组件现在可以发现,当只修改
count
值的时候,将不会触发Child
组件的render
.