手写react11-PureComponent、memo、createPortal

268 阅读1分钟

react的每次更新都是从根组件开始

class ClassCounter extends React.PureComponent{
  render(){
    return <div>ClassCounter:{this.props.count}</div>
  }
}
function FunctionCounter(props){
  return <div>FunctionCounter:{props.count}</div>
}
const MemoFunctionCounter = React.memo(FunctionCounter);
class App extends React.Component{
  state = {number:0}
  amountRef = React.createRef()
  handleClick = ()=>{
    let nextNumber = this.state.number + (parseInt(this.amountRef.current.value));
    this.setState({
      number:nextNumber
    });
  }
  render(){
    return (
      <div>
        <ClassCounter count={this.state.number}/>
        <MemoFunctionCounter count={this.state.number}/>
        <input ref={this.amountRef}/>
        <button onClick={this.handleClick}>+</button>
      </div>
    )
  }
}
ReactDOM.render(<App/>, document.getElementById('root'));

上面的案例中,如果input里面输入了0,那么点击➕按钮,重新渲染组件,前后两次属性完全一样,所以没必要再渲染,但如果不做任何处理,react还是会再渲染一遍

此时,就可以通过让组件继承React.PureComponent(如上述代码所示)来解决

而对于函数组件来说,可以用React.memo包一下(如上述代码所示)来解决

class PureComponent extends Component{
    shouldComponentUpdate(nextProps,nextState){
        //只要属性和状态对象,有任意一个属性变了,就会进行更新。如果全相等,才不更新
        return !shallowEquals(this.props,nextProps) || !shallowEquals(this.state,nextState)
    }
}
function memo(type,compare=shallowEquals){
    return {
        $$typeof:REACT_MEMO,
        type,//函数组件
        compare
    }
}
const React = {
    createElement,
    Component,
    createRef,
    forwardRef,
    Fragment:REACT_FRAGMENT,
    createContext,
    PureComponent,
    memo
}

初次创建DOM时,增加对REACT_MEMO类型的支持:

export function createDOM(vdom) {
    if (!vdom) return null;
    let { type, props, ref } = vdom;
    let dom;//真实DOM
    if(type && type.$$typeof === REACT_MEMO){
        return mountMemo(vdom);
function mountMemo(vdom){
    //type = {$$typeof:REACT_MEMO,type,//函数组件compare}
    let { type, props } = vdom; // type.type 函数组件
    let renderVdom = type.type(props);
    vdom.prevProps= props;//在vdom记录上一次的属性对象
    vdom.oldRenderVdom = renderVdom;//findDOM的时候用的
    return createDOM(renderVdom);
}

更新DOM时,增加对REACT_MEMO类型的支持:

function updateElement(oldVdom, newVdom) {
    //Provider更新
    if(oldVdom.type.$$typeof === REACT_MEMO){
        updateMemo(oldVdom, newVdom);
function updateMemo(oldVdom, newVdom){
    let {type,prevProps} = oldVdom;
    //比较结果是相等,就不需要重新渲染 render
    let renderVdom=oldVdom.oldRenderVdom;
    if(!type.compare(prevProps,newVdom.props)){
        let currentDOM = findDOM(oldVdom);
        let parentDOM = currentDOM.parentNode;
        let {type,props} = newVdom;
        renderVdom = type.type(props);
        compareTwoVdom(parentDOM,oldVdom.oldRenderVdom,renderVdom);
    }
    newVdom.prevProps = newVdom.props;
    newVdom.oldRenderVdom = renderVdom;
}

对于弹框这种交互,不应该把DOM渲染到子组件中,而应该插入到body中,react通过createPortal实现这一点

class Dialog extends React.Component{
  constructor(props){
    super(props);
    this.node = document.createElement('div');
    document.body.appendChild(this.node);
  }
  componentWillUnmount(){
    document.body.removeChild(this.node);
  }
  
  render(){
    //把一个JSX,也就是一个虚拟DoM元素渲染到地应的DOM节点中
    return ReactDOM.createPortal(
      <div className="dialog">
        {this.props.children}
      </div>,
      this.node
    );
  }
}
class App extends React.Component{
  render(){
    return (
      <div>sss
        <Dialog>模态窗口</Dialog>
      </div>
    )
  }
}
ReactDOM.render(<App/>, document.getElementById('root'));

实现:

const ReactDOM = {
    render,
    createPortal:render