Update

Update 활성화

  • ReactDOM.render
  • this.setState
  • this.forceUpdate
  • useState
  • useReducer

Update 프로세스

this.update.enqueueSetState 통해서 Update를 생성하고 실행한다.

Update 활성화
    |
    v
shared.pending 생성
    |
    v
....
    |
    v
updateQueue 처리(completeWork)
    |
    v
...

Update객체

// ClassComponent, HostRoot
const update = {
  eventTime: "",
  lane: "",
  suspenseConfig: "",
  tag: UpdateState, // CaptureUpdate, ForceUpdate, ReplaceState, UpdateState
  payload: null, // ClassComponent this.setState args[0], HostRoot ReactDOM.render args[0]
  callback: null, // ClassComponent this.setState args[1], HostRoot ReactDOM.render args[2]
  next: null,
};

// FunctionComponent
const update = {
  eventTime: "",
  lane: "",
  suspenseConfig: "",
  action: "", // ????
  eagerReducer: null, // useState는 basicStateReducer사용.
  eagerState: null, // 리듀서를 통해 action의 결과값을 얻는다. basicStateReducer(baseState, action)
  next: null,
};

function basicStateReducer(state, action) {
  return typeof action === "function" ? action(state) : action;
}

Update Queue

  • baseState: 메모리에 임시 저장되는 값.
  • memoizedState: 화면에 노출되는 값
  • shared.pending: 업데이트 활성화시 임시 저장하고 우선순위에 적합한 Update만 firstBaseUpdate뒤에 추가.
// ClassComponent
const Fiber = {
  updateQueue: {
    baseState: '',
    firstBaseUpdate: Update,
    lastBaseUpdate: Update,
    shared: {
      pending: Updates
    }
    effects: [] // update.callback 이 존재하는 경우만 배열에 추가
  }
  memoizedState: stateValue
}

// FunctionComponent
const Fiber = {
  memoizedState: { // Hook
    memoizedState: stateValue,
    baseState: '',
    baseQueue: Update,
    queue: Updates,
    next, // Hook list
  }
}

update 종류별

단일 Task

const App = () => {
  const [count, setCount] = useState(0);
  const onClick = () => setCount(count + 1);
  return <div onClick={onClick}>{count}</div>;
};

batchUpdate

const App = () => {
  const [count, setCount] = useState(0);
  const onClick1 = () => {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  };
  const onClick2 = () => {
    setCount((baseState) => baseState + 1);
    setCount((baseState) => baseState + 1);
    setCount(count + 1);
    setCount((baseState) => baseState + 1);
  };
  const onClick3 = () => {
    setTimeout(() => {
      // concurrent mode일 경우 배치됨, legacy mode일 경우 3번 랜딩됨.
      setCount(count + 1);
      setCount(count + 1);
      setCount(count + 1);
    }, 0);
  };
  return (
    <>
      <h1>{count}</h1>
      <div onClick={onClick1}>click1</div>
      <div onClick={onClick2}>click2</div>
      <div onClick={onClick3}>click3</div>
    </>
  );
};

high priority task

const arr = [];
arr.length = 6000;
arr.fill(0);

const App = () => {
  const btnRef = useRef(null);
  const [count, setCount] = useState(0);
  const onClick = () => {
    setCount((count) => count + 1);
  };

  useEffect(() => {
    // 레거시 모드에서는 10, 11로 화면에 노출
    // 동시성 모드에서는 1, 11로 화면에 노출(1차 우선순위 높은거 실행, 2차 순차적으로 실행.)
    // [10_2, x+1_1]
    setTimeout(() => {
      setCount(count + 10);
    }, 2000);
    setTimeout(() => {
      btnRef.current.click();
    }, 2000);
  }, []);
  return (
    <>
      <h1>{count}</h1>
      <div
        onClick={onClick}
        ref={btnRef}
      >
        click1
      </div>
      {arr.map(() => (
        <div>{count}</div>
      ))}
    </>
  );
};

복합적인 경우

// For example:
//
//   Given a base state of '', and the following queue of updates
//
//     A1 - B2 - C1 - D2
//
//   where the number indicates the priority, and the update is applied to the
//   previous state by appending a letter, React will process these updates as
//   two separate renders, one per distinct priority level:
//
//   First render, at priority 1:
//     Base state: ''
//     Updates: [A1, C1]
//     Result state: 'AC'
//
//   Second render, at priority 2:
//     Base state: 'A'            <-  The base state does not include C1,
//                                    because B2 was skipped.
//     Updates: [B2, C1, D2]      <-  C1 was rebased on top of B2
//     Result state: 'ABCD'

참고