setState(同步、异步、数据合并)--源码调试

678 阅读5分钟

同步、异步

分析

调试demo

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 1
    }
  }

  handelBtn = () => {
    // 异步写法
    this.setState({
      number: this.state.number + 1
    }, () => {
      console.log(this.state.number, 'callback1');
    })
    console.log(this.state.number, 'log1');

    this.setState({
      number: this.state.number + 1
    }, () => {
      console.log(this.state.number, 'callback2');
    })
    console.log(this.state.number, 'log2');


    this.setState({
      number: this.state.number + 1
    }, () => {
      console.log(this.state.number, 'callback3');
    })
    console.log(this.state.number, 'log3');

    // 同步写法
    setTimeout(() => {
      this.setState({
        number: this.state.number + 1
      }, () => {
        console.log(this.state.number, 'callback1');
      })
      console.log(this.state.number, 'log1');
      debugger
      this.setState({
        number: this.state.number + 1
      }, () => {
        console.log(this.state.number, 'callback2');
      })
      console.log(this.state.number, 'log2');

      this.setState({
        number: this.state.number + 1
      }, () => {
        console.log(this.state.number, 'callback3');
      })
      console.log(this.state.number, 'log3');
    }, 0);
  }

  render () {
    return (
      <div>
        <button onClick={() => this.handelBtn()}  >+1</button>
        <div>{this.state.number}</div>
      </div>
    )
  }
}

输出
1 'log1'
1 'log2'
1 'log3'
2 'callback1'
2 'callback2'
2 'callback3'

流程图

关键阶段分析

1、点击事件会分发进入到批量处理函数

export function batchedUpdates<A, R>(fn: A => R, a: A): R {
  const prevExecutionContext = executionContext;
  // 将批量处理标记记录在executionContext中
  executionContext |= BatchedContext;
  try {
    // 执行onclick
    return fn(a);
  } finally {
    executionContext = prevExecutionContext;
    // If there were legacy sync updates, flush them at the end of the outer
    // most batchedUpdates-like method.
    if (
      executionContext === NoContext &&
      // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.
      !(__DEV__ && ReactCurrentActQueue.isBatchingLegacy)
    ) {
      resetRenderTimer();
      // onClick全部处理完后执行
      flushSyncCallbacksOnlyInLegacyMode();
    }
  }
}

从dipathcEvent到onClilck堆栈

2、执行onClick中的setState

const classComponentUpdater = {
  isMounted,
  enqueueSetState(inst, payload, callback) {
    const fiber = getInstance(inst);
    const eventTime = requestEventTime();
    const lane = requestUpdateLane(fiber);
		
    // 生成此次setState的updape
    const update = createUpdate(eventTime, lane);
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'setState');
      }
      update.callback = callback;
    }
		// 将这次update放入filber中的更新列表中
    enqueueUpdate(fiber, update, lane);
    // 对filber进行调度
    
    const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
    
    if (enableSchedulingProfiler) {
      markStateUpdateScheduled(fiber, lane);
    }
  },
};

\

3、scheduleUpdateOnFiber,

  • setState的执行告一段落,将这次更新挂在filber的更新队列中
  • 根据executionContext是否打上批量跟新标签
  • 执行onClick中的console.log函数,然后依次重复执行完三次,输出三次打印(异步)
  • 当Click全部执行完后,回到batchedUpdates中的finally阶段(异步)
  • 执行flushSyncCallbacksOnlyInLegacyMode(同步)
export function scheduleUpdateOnFiber(
  fiber: Fiber,
  lane: Lane,
  eventTime: number,
): FiberRoot | null {
	//...
  // 由于在batchedUpdates中executionContext加上了批量处理标签为1,而NoContext等于0,无法进入判断函数。
  // 同步的情况下未经过batchedUpdates,可直接进入判断执行flushSyncCallbacksOnlyInLegacyMode
  if (
    lane === SyncLane &&
    executionContext === NoContext &&
    (fiber.mode & ConcurrentMode) === NoMode &&
    // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.
    !(__DEV__ && ReactCurrentActQueue.isBatchingLegacy)
  ) {
    resetRenderTimer();
    // 同步执行setState
    flushSyncCallbacksOnlyInLegacyMode();
  }

  return root;
}

4、 flushSyncCallbacksOnlyInLegacyMode到processUpdateQueue

  • (异步) 存储的三个setState对应的update,最后将运行effect链,执行三个setState的回调函数

  • (同步) 此时update链中只有当前的setState更新,后面的异步更新一样,执行完update链后再执行effect链

执行完后再执行console.log操作,再继续执行setState,所以每个setState是一个完整更新回合,没有存储在update链中异步同一执行。

\

export function processUpdateQueue<State>(
  workInProgress: Fiber,
  props: any,
  instance: any,
  renderLanes: Lanes,
): void {
  const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);

  let firstBaseUpdate = queue.firstBaseUpdate;
  let lastBaseUpdate = queue.lastBaseUpdate;

  let pendingQueue = queue.shared.pending;
  if (pendingQueue !== null) {
    queue.shared.pending = null;
    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 (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;
			
      // 将setState的回调函数放入effects链中
      const callback = update.callback;
        if (
          callback !== null &&
          // If the update was already committed, we should not queue its
          // callback again.
          update.lane !== NoLane
        ) {
          workInProgress.flags |= Callback;
          const effects = queue.effects;
          if (effects === null) {
            queue.effects = [update];
          } else {
            effects.push(update);
          }
        }
      }
    	// 对下一个update进行处理
      update = update.next;
      if (update === null) {

      }
    } while (true);

    if (newLastBaseUpdate === null) {
      newBaseState = newState;
    }
}

总结

  1. 异步和同步主要区别是在看flushSyncCallbacksOnlyInLegacyMode函数在那调用
  2. 异步:由于executionContext在打上了批量更新标签,所以会等到onClick内函数全部执行完后在finally执行flushSyncCallbacksOnlyInLegacyMode函数,此时的三次setState会记录在updateQueue中,最后异步依次执行。
  1. 同步,由于setState触发的上下文是在setTimeout中,没有打上了任何标签,在scheduleUpdateOnFiber中进入判断执行flushSyncCallbacksOnlyInLegacyMode函数,此时立即执行updateQueue(只当当前一个updata),整个update运行完后在到console,log函数。

数据合并

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0
    }
  }

  render() {
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
      </div>
    )
  }

  increment() {
    // 1.setState本身被合并,最终运算还是1
    this.setState({
      counter: this.state.counter + 1
    });
    this.setState({
      counter: this.state.counter + 1
    });
    this.setState({
      counter: this.state.counter + 1
    });

    // 2.setState合并时进行累加,最终运算3
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    });
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    });
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    });
  }
}

分析

1、setState

  • 此时的partialState是{ count : this.state.counter + 1 }的结果,{ count : 2 }。
  • 在enqueueSetState中将partialState挂在在update.payload中。
  • 根据批量处理方式(异步),三次运行setState时,this.state并没变化,所以三个update.payload都是{ count : 2 }。
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');
};
const classComponentUpdater = {
  isMounted,
  enqueueSetState(inst, payload, callback) {
    const fiber = getInstance(inst);
    const eventTime = requestEventTime();
    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, lane);
    const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
  },
};

2、ReactUpdateQueue

  • 通过do while循环将updateQueue队列依次执行,并每次使用newStata更新state
  • 在UpdateState过程中分setState传入是对象还是函数
  1. 函数:会将更新前的preState传入函数中,partialState等于函数返回值。
  2. 对象:直接将update.payload赋值给partialState,由1中可知三次的update.payload都是{ count : 2 }。
export function processUpdateQueue<State>(
  workInProgress: Fiber,
  props: any,
  instance: any,
  renderExpirationTime: ExpirationTime,
): void {
 	// ...
  if (baseQueue !== null) {
    let first = baseQueue.next;
    let newState = queue.baseState;

    if (first !== null) {
      let update = first;
      do {
        	//...
          newState = getStateFromUpdate(
            workInProgress,
            queue,
            update,
            newState,
            props,
            instance,
          );
         //...
        update = update.next;
        if (update === null || update === first) {
          pendingQueue = queue.shared.pending;
          if (pendingQueue === null) {
            break;
          } else {
            update = baseQueue.next = pendingQueue.next;
            pendingQueue.next = first;
            queue.baseQueue = baseQueue = pendingQueue;
            queue.shared.pending = null;
          }
        }
      } while (true);
    }
		//...
}
function getStateFromUpdate<State>(
  workInProgress: Fiber,
  queue: UpdateQueue<State>,
  update: Update<State>,
  prevState: State,
  nextProps: any,
  instance: any,
): any {
  console.log('123');

  switch (update.tag) {
    // Intentional fallthrough
    case UpdateState: {
      const payload = update.payload;
      let partialState;
      if (typeof payload === 'function') {
   			// 情况二,使用方法调用更新state
        // 第一轮:prevState:0,partialState:1
        // 第二轮:prevState:1,partialState:2
        // 第三轮:prevState:2,partialState:3
        partialState = payload.call(instance, prevState, nextProps);
        if (__DEV__) {
       
      	} else {
        // Partial state object
        partialState = payload;
      }
      if (partialState === null || partialState === undefined) {
        // Null and undefined are treated as no-ops.
        return prevState;
      }
    	// 情况一:使用对象形式更行
      // 由于partialState传入的是this.state+1,而this.state在循环队列中并没有更行
      // 第一轮 Object.assign({}, prevState:0, partialState:1);
      // 第二轮 Object.assign({}, prevState:1, partialState:1);
      // 第三轮 Object.assign({}, prevState:1, partialState:1);
      // 最终还是返回1
      return Object.assign({}, prevState, partialState);
    }
    case ForceUpdate: {
      hasForceUpdate = true;
      return prevState;
    }
  }
  return prevState;
}