[react]9、setState

2021-11-07  本文已影响0人  史记_d5da

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);
    });
}

小结:

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'],
  }));
};
上一篇 下一篇

猜你喜欢

热点阅读