美文网首页
[react]9、setState

[react]9、setState

作者: 史记_d5da | 来源:发表于2021-11-07 09:18 被阅读0次

    1、获取setState异步更新后的结果

    1、setState更新后一个回调函数,可以通过回调函数获取更新后的值

    this.setState({
      counter: this.state.counter + 1
    }, () => { // 
      console.log('更新完成!')
    })
    

    2、componentDidUpdate() 生命周期函数可以获取回更新后的值

    componentDidUpdate() {
      console.log(this.state.counter);
    }
    

    2、实现setState同步更新

    1、setState用定时器包裹,设置定时器时间为0

    setTimeout(() => {
        this.setState({
          counter: this.state.counter + 1
        }, () => {
        });
        console.log(this.state.counter);
    }, 0);
    

    2、通过监听原生的DOM事件会滴

     componentDidMount() {
       document.getElementById("btn").addEventListener("click", ()=> {
          this.setState({
               counter: this.state.counter + 1
           }, () => {
           })
           console.log(this.state.counter);
        });
    }
    

    小结:

    • 在组件生命周期或React合成事件中,setState是异步
    • 在setTimeout或者原生dom事件中,setState是同步
      例如
    import React, { Component } from 'react'
    
    export default class App extends Component {
        constructor(props) {
            super(props)
            this.state = {
                message: "好消息啊"
            }
        }
        componentDidUpdate() {
        }
        componentDidMount() {
            document.getElementById("btn").addEventListener("click", ()=> {
                this.setState({
                    message: "你好啊,领导们"
                }, () => {
                })
                console.log(this.state.message)  
            })
        }
        render() {
            return (
                <div style={{flexDirection: "column"}}>
                    <h2>{this.state.message}</h2>
                     {/*react 的合成事件*/}
                    <button onClick={e => this.changeText()}>改变文本</button>
                    <button  id={"btn"} >改变文本2</button>
                </div>
            )
        }
    
        changeMessage() {
            setTimeout(() => {
                this.setState({
                    message: "你好啊,领导们"
                }, () => {
                })
                console.log(this.state.message)
            }, 0);
        }
    }
    

    源码分析setState

    平时看源码时主要是看 react、react-dom、react-reconciler 三个文件夹

    setState的调用入口

    // react/src/ReactBaseClasses.js line 57
    Component.prototype.setState = function(partialState, callback) {
      invariant(
        typeof partialState === 'object' ||
          typeof partialState === 'function' ||
          partialState == null,
        'setState(...): takes an object of state variables to update or a ' +
          'function which returns an object of state variables.',
      );
      this.updater.enqueueSetState(this, partialState, callback, 'setState');
    };
    

    this.updater调用的是 ReactFiberClassComponent.new.js中的enqueueSetState方法

    // react-reconciler/src/forks/ReactFiberClassComponent.new.js line194
    const classComponentUpdater = {
      isMounted,
      enqueueSetState(inst, payload, callback) {
        const fiber = getInstance(inst);
        const eventTime = requestEventTime();
        // line 199 返回确定是使用同步还是异步执行的方式
        const lane = requestUpdateLane(fiber);
    
        const update = createUpdate(eventTime, lane); // 返回记录要更新的内容
        update.payload = payload;
        if (callback !== undefined && callback !== null) {
          if (__DEV__) {
            warnOnInvalidCallback(callback, 'setState');
          }
          update.callback = callback;
        }
    
        enqueueUpdate(fiber, update); // 将本次需要update的内容加入到队列中
        scheduleUpdateOnFiber(fiber, lane, eventTime);
        ... // 省略
      },
    };
    

    查看requestUpdateLane调用方法,返回SyncLane同步或者SyncBatchedLane批处理

    //  react-reconciler/src/forks/ReactFiberWorkLoop.new.js line 506
    function requestRetryLane(fiber: Fiber) {
      // This is a fork of `requestUpdateLane` designed specifically for Suspense
      // "retries" — a special update that attempts to flip a Suspense boundary
      // from its placeholder state to its primary/resolved state.
    
      // Special cases
      const mode = fiber.mode;
      if ((mode & BlockingMode) === NoMode) {
        return (SyncLane: Lane); // 返回执行方式
      } else if ((mode & ConcurrentMode) === NoMode) {
        return getCurrentPriorityLevel() === ImmediateSchedulerPriority
          ? (SyncLane: Lane)
          : (SyncBatchedLane: Lane); // 批处理
      }
    
      // See `requestUpdateLane` for explanation of `currentEventWipLanes`
      if (currentEventWipLanes === NoLanes) {
        currentEventWipLanes = workInProgressRootIncludedLanes;
      }
      return findRetryLane(currentEventWipLanes);
    }
    

    查看enqueueUpdate 方法

    // react-reconciler/src/forks/ReactUpdateQueue.new.js line 506
    export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
      const updateQueue = fiber.updateQueue;
      if (updateQueue === null) {
        // Only occurs if the fiber has been unmounted.
        return;
      }
    
      const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
      const pending = sharedQueue.pending;
      if (pending === null) {
        // This is the first update. Create a circular list.
        update.next = update; // 将需要update的内容组成一个链表
      } else {
        update.next = pending.next;
        pending.next = update;
      }
      sharedQueue.pending = update;
    }
    

    3、setState数据合并

    同时执行多条setState的时候底层源码会进行合并操作
    1、执行的setState为对象

    this.setState({
       couner: prevState.couner + 1
    })
    
    this.setState({
        couner: prevState.couner + 1
    })
    
    this.setState({
        couner: prevState.couner + 1
    })
    

    2、执行setState是一个函数

    this.setState((prevState, props) => {
       return {
           couner: prevState.couner + 1
       }
    })
    
    this.setState((prevState, props) => {
        return {
            couner: prevState.couner + 1
        }
    })
    
    this.setState((prevState, props) => {
        return {
              couner: prevState.couner + 1
        }
    })
    

    源码分析

    1)、执行的setState为对象时其实是通过do..while循环多次调用getStateFromUpdate方法
    执行 Object.assign({}, prevState, partialState),每次执行的时候prevState都是原来的state,而不是上一条state,因此最终合并成最后一次执行的setState方法。
    2)、执行setState是一个函数,getStateFromUpdate会判断typeof payload === 'function'是否是一个函数,如果是一个函数,直接执行方法payload.call(instance, prevState, nextProps);,对每一条count进行+1操作

    // react-reconciler/src/forks/ReactUpdateQueue.new.js line 384
    // line 311
    function getStateFromUpdate<State>(
      workInProgress: Fiber,
      queue: UpdateQueue<State>,
      update: Update<State>,
      prevState: State,
      nextProps: any,
      instance: any,
    ): any {
      switch (update.tag) {
        case ReplaceState: {
          const payload = update.payload;
          if (typeof payload === 'function') {
            // Updater function
            const nextState = payload.call(instance, prevState, nextProps);
            return nextState;
          }
          // State object
          return payload;
        }
        case CaptureUpdate: {
          workInProgress.flags =
            (workInProgress.flags & ~ShouldCapture) | DidCapture;
        }
        // Intentional fallthrough
        case UpdateState: {
          const payload = update.payload;
          let partialState;
          if (typeof payload === 'function') {
            // Updater function
            partialState = payload.call(instance, prevState, nextProps);
          } else {
            // Partial state object
            partialState = payload;
          }
          if (partialState === null || partialState === undefined) {
            // Null and undefined are treated as no-ops.
            return prevState;
          }
          // Merge the partial state and the previous state.
          return Object.assign({}, prevState, partialState); // 合并state数据
        }
        case ForceUpdate: {
          hasForceUpdate = true;
          return prevState;
        }
      }
      return prevState;
    }
    

    processUpdateQueue方法的do..while循环操作

    // react-reconciler/src/forks/ReactUpdateQueue.new.js line 394
    export function processUpdateQueue<State>(
      workInProgress: Fiber,
      props: any,
      instance: any,
      renderLanes: Lanes,
    ): void {
      // This is always non-null on a ClassComponent or HostRoot
      const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
    
      hasForceUpdate = false;
    
      let firstBaseUpdate = queue.firstBaseUpdate;
      let lastBaseUpdate = queue.lastBaseUpdate;
    
      // Check if there are pending updates. If so, transfer them to the base queue.
      let pendingQueue = queue.shared.pending;
      if (pendingQueue !== null) {
        queue.shared.pending = null;
    
        // The pending queue is circular. Disconnect the pointer between first
        // and last so that it's non-circular.
        const lastPendingUpdate = pendingQueue;
        const firstPendingUpdate = lastPendingUpdate.next;
        lastPendingUpdate.next = null;
        // Append pending updates to base queue
        if (lastBaseUpdate === null) {
          firstBaseUpdate = firstPendingUpdate;
        } else {
          lastBaseUpdate.next = firstPendingUpdate;
        }
        lastBaseUpdate = lastPendingUpdate;
    
        // If there's a current queue, and it's different from the base queue, then
        // we need to transfer the updates to that queue, too. Because the base
        // queue is a singly-linked list with no cycles, we can append to both
        // lists and take advantage of structural sharing.
        // TODO: Pass `current` as argument
        const current = workInProgress.alternate;
        if (current !== null) {
          // This is always non-null on a ClassComponent or HostRoot
          const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
          const currentLastBaseUpdate = currentQueue.lastBaseUpdate;
          if (currentLastBaseUpdate !== lastBaseUpdate) {
            if (currentLastBaseUpdate === null) {
              currentQueue.firstBaseUpdate = firstPendingUpdate;
            } else {
              currentLastBaseUpdate.next = firstPendingUpdate;
            }
            currentQueue.lastBaseUpdate = lastPendingUpdate;
          }
        }
      }
    
      // These values may change as we process the queue.
      if (firstBaseUpdate !== null) {
        // Iterate through the list of updates to compute the result.
        let newState = queue.baseState;
        // TODO: Don't need to accumulate this. Instead, we can remove renderLanes
        // from the original lanes.
        let newLanes = NoLanes;
    
        let newBaseState = null;
        let newFirstBaseUpdate = null;
        let newLastBaseUpdate = null;
    
        let update = firstBaseUpdate;
        do {
          const updateLane = update.lane;
          const updateEventTime = update.eventTime;
          if (!isSubsetOfLanes(renderLanes, updateLane)) {
            // Priority is insufficient. Skip this update. If this is the first
            // skipped update, the previous update/state is the new base
            // update/state.
            const clone: Update<State> = {
              eventTime: updateEventTime,
              lane: updateLane,
    
              tag: update.tag,
              payload: update.payload,
              callback: update.callback,
    
              next: null,
            };
            if (newLastBaseUpdate === null) {
              newFirstBaseUpdate = newLastBaseUpdate = clone;
              newBaseState = newState;
            } else {
              newLastBaseUpdate = newLastBaseUpdate.next = clone;
            }
            // Update the remaining priority in the queue.
            newLanes = mergeLanes(newLanes, updateLane);
          } else {
            // This update does have sufficient priority.
    
            if (newLastBaseUpdate !== null) {
              const clone: Update<State> = {
                eventTime: updateEventTime,
                // This update is going to be committed so we never want uncommit
                // it. Using NoLane works because 0 is a subset of all bitmasks, so
                // this will never be skipped by the check above.
                lane: NoLane,
    
                tag: update.tag,
                payload: update.payload,
                callback: update.callback,
    
                next: null,
              };
              newLastBaseUpdate = newLastBaseUpdate.next = clone;
            }
    
            // Process this update.
            // 合并所有的setState方法,将其合并成一个state
            newState = getStateFromUpdate(
              workInProgress,
              queue,
              update,
              newState,
              props,
              instance,
            );
            const callback = update.callback;
            if (callback !== null) {
              workInProgress.flags |= Callback;
              const effects = queue.effects;
              if (effects === null) {
                queue.effects = [update];
              } else {
                effects.push(update);
              }
            }
          }
          update = update.next;
          if (update === null) {
            pendingQueue = queue.shared.pending;
            if (pendingQueue === null) {
              break;
            } else {
              // An update was scheduled from inside a reducer. Add the new
              // pending updates to the end of the list and keep processing.
              const lastPendingUpdate = pendingQueue;
              // Intentionally unsound. Pending updates form a circular list, but we
              // unravel them when transferring them to the base queue.
              const firstPendingUpdate = ((lastPendingUpdate.next: any): Update<State>);
              lastPendingUpdate.next = null;
              update = firstPendingUpdate;
              queue.lastBaseUpdate = lastPendingUpdate;
              queue.shared.pending = null;
            }
          }
        } while (true);
    
        if (newLastBaseUpdate === null) {
          newBaseState = newState;
        }
    
        queue.baseState = ((newBaseState: any): State);
        queue.firstBaseUpdate = newFirstBaseUpdate;
        queue.lastBaseUpdate = newLastBaseUpdate;
    
        // Set the remaining expiration time to be whatever is remaining in the queue.
        // This should be fine because the only two other things that contribute to
        // expiration time are props and context. We're already in the middle of the
        // begin phase by the time we start processing the queue, so we've already
        // dealt with the props. Context in components that specify
        // shouldComponentUpdate is tricky; but we'll have to account for
        // that regardless.
        markSkippedUpdateLanes(newLanes);
        workInProgress.lanes = newLanes;
        workInProgress.memoizedState = newState;
      }
    }
    

    4、setState的不可变力量

    使用延展操作符对数组进行深拷贝

    handleClick() {
      this.setState(state => ({
        words: [...state.words, 'marklar'],
      }));
    };
    

    相关文章

      网友评论

          本文标题:[react]9、setState

          本文链接:https://www.haomeiwen.com/subject/ydhszltx.html