【React】React源码梳理笔记(六)

前言

  • 继续上次React,这次主要搞生命周期

原版示例对照

  • 做生命周期思路其实不难,在更新的地方已经可以拿到类的实例,然后进行调用判断即可。
  • 先做个示例对照:
class Child extends React.Component{
  componentWillMount(){
    console.log('child willmount')
  }
  componentDidMount(){
    console.log('child didmount')
  }
  shouldComponentUpdate(nextProps,nextState){
    console.log('child shouldmount')
    return nextProps.number>2
  }
  componentWillUpdate(){
    console.log('child willupdate')
  }
  componentDidUpdate(){
    console.log('child didupdate')
  }
  componentWillUnmount(){
    console.log('child unmount')
  }
  render(){
    console.log('childrender')
    return <div>{this.props.number}</div>
  }
}
class Counter extends React.Component{
  static defaultProps = {name:'yehuozhili'}
  constructor(props){
    super(props)
    this.state={number:0}
    console.log('xxx')
  }
  componentWillMount(){
    console.log('willmount')
  }
  componentDidMount(){
    console.log('didmount')
  }
  shouldComponentUpdate(nextProps,nextState){
    console.log('shouldmount')
    return nextState.number>1
  }
  componentWillUpdate(){
    console.log('willupdate')
  }
  componentDidUpdate(){
    console.log('didupdate')
  }
  handleClick=()=>{
    this.setState((state)=>({number:state.number+1}))
  }
  render(){
    console.log('render')
    return (
      <div>
        <p>{this.state.number}</p>
        {
          this.state.number>3?null:<Child number={this.state.number}></Child>
        }
        <button onClick={this.handleClick}>+</button>
      </div>
    )
  }
}
ReactDOM.render(
  <Counter></Counter>,
  document.getElementById('root')
);
xxx
index.js:37 willmount
index.js:56 render
index.js:6 child willmount
index.js:25 childrender
index.js:9 child didmount
index.js:40 didmount
2index.js:43 shouldmount
index.js:47 willupdate
index.js:56 render
index.js:12 child shouldmount
index.js:50 didupdate
index.js:43 shouldmount
index.js:47 willupdate
index.js:56 render
index.js:12 child shouldmount
index.js:16 child willupdate
index.js:25 childrender
index.js:19 child didupdate
index.js:50 didupdate
index.js:43 shouldmount
index.js:47 willupdate
index.js:56 render
index.js:22 child unmount
index.js:50 didupdate

实现willmount didmount

  • willmount和willupdate会和getSnapshotBeforeUpdate冲突,所以后面在搞这个。
  • 这2插入地方在创建类组件真实dom处:
function createClassComponetDOM(element){
    let {type,props}=element
    let componentInstance =new  type(props)
    if(componentInstance.componentWillMount){
        componentInstance.componentWillMount()
    }
    let renderElement = componentInstance.render()
    componentInstance.renderElement=renderElement
    element.componentInstance=componentInstance
    let newDom =createDOM(renderElement)
    if(componentInstance.componentDidMount){
        componentInstance.componentDidMount()
    }
    return newDom
}

实现willupdate didupdate

  • 这个上次实现了,位置在Component的forceupdate里面:
Component.prototype.forceUpdate = function() {
   let {renderElement}=this//拿到虚拟dom
   if(this.componentWillUpdate){
       this.componentWillUpdate()
   }
   let newRenderElement =this.render()//拿到新状态下的组件虚拟dom结果
   let currentElement =compareTwoElement(renderElement,newRenderElement)//比较新老虚拟dom
   this.renderElement = currentElement
   if(this.componentDidUpdate){
       this.componentDidUpdate()
   }
};

实现shouldComponentUpdate

  • 这个上次也实现了,位置在将执行强制更新逻辑处:
function shouldUpdate(componentInstance,nextProps,nextState){//nextState就是最新state
    let scu=componentInstance.shouldComponentUpdate&&!componentInstance.shouldComponentUpdate(nextProps,nextState)
    componentInstance.props =nextProps
    componentInstance.state = nextState//让其有新属性
    if(scu){
        return false
    }    
    componentInstance.forceUpdate()
}

实现willUnmount

  • 挂载点在domdiff函数上,如果新Map上没有这个索引的元素,就卸载掉。
function diff(parentNode,oldChildrenElements,newChildrenElements,diffQueue){
    let oldChildrenElementsMap=getChildrenElementsMap(oldChildrenElements)
    let newChildrenElementsMap=getNewChildrenElementsMap(newChildrenElements,oldChildrenElementsMap)
    let lastIndex=0
    for(let i=0;i<newChildrenElements.length;i++){
        let newChildElement=newChildrenElements[i]
        if(newChildElement){
            let newKey=newChildElement.key||i.toString()
            let oldChildElement=oldChildrenElementsMap[newKey]
            if(newChildElement===oldChildElement){//说明同一个节点,因为前面获取newmap操作有个赋值,直接把老节点拿过来复用
                if(oldChildElement._mountIndex<lastIndex){//挂载点小于说明要移动,等于大于不动,后面会把lastindex调到最大
                    diffQueue.push({//挂载点小于的情况是在lastindex之间的元素移动到后面
                        parentNode,//也就是Lastindex代表不用动的最后一个节点,之间的都得移动或者删除
                        type:MOVE,
                        fromIndex:oldChildElement._mountIndex,//原挂载点
                        toIndex:i
                    })
                }
                lastIndex=Math.max(oldChildElement._mountIndex,lastIndex)//老的挂载点和最后一个不要移动的之间最大值
            }else{//新老元素不相等,直接插入
                diffQueue.push({
                    parentNode,
                    type:INSERT,
                    toIndex:i,
                    dom:createDOM(newChildElement)
                })
            }
            newChildElement._mountIndex=i //更新新的挂载点
        }else{
            let newKey=i.toString()
            if(oldChildrenElementsMap[newKey].componentInstance&&oldChildrenElementsMap[newKey].componentInstance.componentWillUnmount){
                oldChildrenElementsMap[newKey].componentInstance.componentWillUnmount()
            }
        }
    }
    for(let oldkey in oldChildrenElementsMap){//在老map里遍历如果新Map里没有,就删除
        if(!newChildrenElementsMap.hasOwnProperty(oldkey)){
            let oldChildElement=oldChildrenElementsMap[oldkey]
            diffQueue.push({
                parentNode,
                type:REMOVE,
                fromIndex:oldChildElement._mountIndex,
            })
        }else{//key相同,type不同,也删除
            let oldChildElement = oldChildrenElementsMap[oldkey];
            let newChildElement = newChildrenElementsMap[oldkey];
            if (oldChildElement !== newChildElement) {
                diffQueue.push({
                    parentNode,
                    type: REMOVE,
                    fromIndex: oldChildElement._mountIndex
                });
            }
        }
    }
}

实现componentWillReceiveProps

  • 这个就是可以获得将要收到的属性。
  • 挂载点在更新类组件函数上:
function updateClassComponent(oldelement,newelement){
    let componentInstance = oldelement.componentInstance//拿到实例
    let updater = componentInstance.updater//实例里new的那个updater
    let nextProps = newelement.props  // 新的属性
    if(componentInstance.componentWillReceiveProps){
        componentInstance.componentWillReceiveProps(nextProps)
    }
    updater.emitUpdate(nextProps)//setstate会走这个判断,这个同样直接判断.让updater去更新类组件
}

实现getDerivedStateFromProps

  • 这个生命周期是用来替代上面那个的,是类的静态属性,可以对传入的props做自己的state的映射,如果自己组件没有state,会报警告。
  • 看例子:
class Child extends React.Component{
  constructor(props){
    super(props)
    this.state={number:0}
  }
  static getDerivedStateFromProps(nextProps,prevState){
    const {number}=nextProps
    if(number%2===0){
      return {number:number*2}
    }else{
      return {number:number*3}
    }
  }
  render(){
    console.log('childrender')
    return <div>{this.state.number}</div>
  }
}
class Counter extends React.Component{
  static defaultProps = {name:'yehuozhili'}
  constructor(props){
    super(props)
    this.state={number:0}
  }

  handleClick=()=>{
    this.setState((state)=>({number:state.number+1}))
  }
  render(){
    console.log('render')
    return (
      <div>
        <p>{this.state.number}</p>
        {
          this.state.number>3?null:<Child number={this.state.number}></Child>
        }
        <button onClick={this.handleClick}>+</button>
      </div>
    )
  }
}
  • 父组件每次加一,传入组件判断父组件传的props是奇数还是偶数,奇数3,偶数2。
  • 挂载点和上面一样:
function updateClassComponent(oldelement,newelement){
    let componentInstance = oldelement.componentInstance//拿到实例
    let updater = componentInstance.updater//实例里new的那个updater
    let nextProps = newelement.props  // 新的属性
    if(componentInstance.componentWillReceiveProps){
        componentInstance.componentWillReceiveProps(nextProps)
    }
    if(newelement.type.getDerivedStateFromProps){
        let newState =newelement.type.getDerivedStateFromProps(nextProps,componentInstance.state)
        if(newState){
            componentInstance.state={...componentInstance,...newState}
        }
    }
    updater.emitUpdate(nextProps)//setstate会走这个判断,这个同样直接判断.让updater去更新类组件
}
  • 同时,第一次挂载需要调用一次,挂载点就在创建真实dom那:
function createClassComponetDOM(element){
    let {type,props}=element
    let componentInstance =new  type(props)
    if(componentInstance.componentWillMount){
        componentInstance.componentWillMount()
    }
    if(type.getDerivedStateFromProps){
        let newState =type.getDerivedStateFromProps(props,componentInstance.state)
        if(newState){
            componentInstance.state={...componentInstance,...newState}
        }
    }
    let renderElement = componentInstance.render()
    componentInstance.renderElement=renderElement
    element.componentInstance=componentInstance
    let newDom =createDOM(renderElement)
    if(componentInstance.componentDidMount){
        componentInstance.componentDidMount()
    }
    return newDom
}

实现getSnapshotBeforeUpdate

  • 这个方法配合didupdate使用的,返回值会传给didupdate的第三个参数,所以修改下:
Component.prototype.forceUpdate = function() {
   let {renderElement,props,state}=this//拿到虚拟dom
   if(this.componentWillUpdate){
       this.componentWillUpdate()
   }
   let {getSnapshotBeforeUpdate}=this
   let extra=getSnapshotBeforeUpdate&&getSnapshotBeforeUpdate(props,state)
   let newRenderElement =this.render()//拿到新状态下的组件虚拟dom结果
   let currentElement =compareTwoElement(renderElement,newRenderElement)//比较新老虚拟dom
   this.renderElement = currentElement
   if(this.componentDidUpdate){
       this.componentDidUpdate(props,state,extra)
   }
};

  • 这样基本就完成了
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

业火之理

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值