goidle / goidle.github.io

0 stars 0 forks source link

react/in-depth-react-hooks_1/ #5

Open utterances-bot opened 3 years ago

utterances-bot commented 3 years ago

React 톺아보기 - 03. Hooks_1 | Deep Dive Magic Code

모든 설명은 v16.12.0 버전 함수형 컴포넌트와 브라우저 환경을 기준으로 합니다. 버전에 따라 코드는 변경될 수 있으며 클래스 컴포넌트는 설명에서 제외됨을 알려 드립니다. 각 포스트의 주제는 다음과 같이 리액트의 일반적인 흐름을 따라가도록 구성되었습니다. 포스트별 주제 훅을 통해

https://goidle.github.io/react/in-depth-react-hooks_1/

ellemedit commented 3 years ago

built-in collection이 아닌 연결리스트를 사용하여 구현한 이유는 리스트 탐색 흐름 제어나 노드 삭제 등 조작이 쉽고 리스트 병합에 많은 리소스가 필요하지 않는다는 것입니다. 또한, 랜덤 액세스가 필요한 부분이 없으므로 더욱이 연결리스트를 쓰지 않을 이유가 없습니다.

훅을 사용할 때 컴포넌트가 가진 훅의 길이나 중간에 다른 훅으로 대체되면 안되는데 노드 삭제나 조작은 어떤 상황에서 일어나나요?

goidle commented 3 years ago

훅을 사용할 때 컴포넌트가 가진 훅의 길이나 중간에 다른 훅으로 대체되면 안되는데 노드 삭제나 조작은 어떤 상황에서 일어나나요?

아! 오해하실까 봐 말씀드리지만, 훅 포스트에서 연결 리스트의 장점을 작성하게 되었는데, 여기서 언급된 노드 삭제란 훅 삭제를 가리키는 것이 아닌 리액트에서 전반적으로 사용되는 이유를 말씀드리고 있는 것을 유의하시길 바랍니다.

kimsangyeon commented 3 years ago
function dispatchAction(fiber, queue, action) {
  const alternate = fiber.alternate

  if (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  ) {
    // Render phase update
  } else {
    // idle update
  }
}

위 부분 관련 질문이 있는데요.

  1. 유휴상태라는게 더 이상 소비할 액션이 없는 경우, 업데이트 객체를 생성하고 불필요한 리랜더링 검사 후 업데이트 스케쥴링으로 넘어간다고 이해하면 될까요?

1-1. 더 이상 소비할 액션이 있는 경우가 Render Phase Update?


2. 위 조건문이 renderWithHooks에서 currentlyRenderingFiber를 workInProgress로 현재 작업중인 fiber를 설정한 것과 bind로 잡아둔 fiber와 비교하여 Render phase update 여부 판단을 한다고 이해하는게 맞나요?

2-1. 위의 판단 여부가 맞다면 유휴상태로 가는 조건이 궁금한데 renderWithHooks에서 currentlyRenderingFiber가 이전? 랜더링이 끝나고 commit phase를 지나 새롭게 current로 새롭게 생성된 workInProgress로 변경되어 현재 fiber와 다른경우 인가요?

goidle commented 3 years ago

@kimsangyeon 1 함수형 컴포넌트의 상태를 바꿔주는 dispatchAction()이 어떤 환경에서 실행되느냐에 따라 render phase update와 idle update로 구분할 수 있습니다.

먼저 특정 컴포넌트의 dispatchAction()를 통해 상태 값을 변경하면 리컨실러는 해당 컴포넌트에 대해 재조정 작업을 진행할 것입니다. renderWithHooks()를 통해 해당 컴포넌트를 호출할 텐데 이때 추가적으로 dispatchAction()이 호출된다면 이는 render phase update를 나타내며 fiber === currentlyRenderingFiber || (alternate !== null && alternate === currentlyRenderingFiber) 의 조건을 만족시킬 것입니다. 왜냐면 renderWithHooks()에서 해당 컴포넌트의 fiber를 currentlyRenderingFiber로 잡아두었기 때문이죠.

2 유휴 상태의 업데이트란 해당 컴포넌트가 재조정 중이지 않을 때 발생하는 상태 변경입니다. 이때는 당연히 renderWithHooks()를 호출하지 않았을 것이므로 currentlyRenderingFiber는 비어 있습니다.

kangyongseok commented 2 years ago

와... react 에 useState 가 어떻게 동작하는지 개념확인하고 간단하게 실제 구현까지 해보려고 찾아보던 와중에 보게된 블로그인데 정말 깔끔하게 잘 정리되어있네요 이런 오픈 소스의 구조 파악이나 흐름에 대해서 이해하기위해서 어떤 방식으로 접근하고 풀어나가는지 궁금합니다.

happenask commented 11 months ago
function dispatchAction(fiber, queue, action) {
  /*...*/
  // queue.last = update;

  if (      
      fiber.expirationTime === NoWork &&
      (alternate === null || alternate.expirationTime === NoWork)) {
    const lastRenderedReducer = queue.lastRenderedReducer
    if (lastRenderedReducer !== null) {
      ..............
      if (is(eagerState, currentState)) {
        return
      }
    }
  }
  scheduleWork(fiber, expirationTime)
}

위의 코드를 보면 fiber가 NoWork인지에 대한 조건문 안에 scheduleWork가 있는게 아니고 조건문 밖에서 schduleWork가 실행되고 있는데 어떻게 dispatchAction을 여러번 호출됐을 때 한번만 scheduleWork를 실행하는지 이해되지가 않습니다.

happenask commented 11 months ago

답변 감사합니다. 제가 디버깅한 결과 fiber.expirationTime !== NoWork 이면 아래 조건문 안으로 들어가지 않고 is(eagerState, currentState) 또한 실행되지 않습니다.

if (      
      fiber.expirationTime === NoWork &&
      (alternate === null || alternate.expirationTime === NoWork)) {
    const lastRenderedReducer = queue.lastRenderedReducer
    if (lastRenderedReducer !== null) {
      ..............
      if (is(eagerState, currentState)) {
        return
      }
    }

dispatchAction을 실행하면 그리고 조건문과 상관없이 scheduleWork(fiber, expirationTime) 과 실행되고 있습니다.

goidle commented 11 months ago

@happenask 해당 글을 작성한지 오래다 보니깐 당시의 작성 의도가 기억나지는 않지만 정정해야 될 부분이 두군데 있어서 공유드립니다.

  1. 불필요한 컴포넌트 리-렌더링 방지 섹션에서 dispatchAction()이 여러번 호출될 경우 매 호출 Work가 스케줄링 되는가의 답변은 잘못되었습니다. 아마 당시에 if문 조건을 잘못 이해하고 작성된 것으로 판단됩니다. 해당 부분은 삭제하였습니다.

    fiber.expirationTime === NoWork가 아닌 fiber.expirationTime !== NoWork로 이해한 듯..

    해당 부분을 다시 해석하여 전달드리자면, idle 상태에서 fiber에 expirationTime이 새겨져 있는 상태에서 추가적인 dispatchAction()이 호출된다면, 기존 스케줄링된 Work을 발생시킨 이벤트의 우선순위와 현재 발생한 이벤트의 우선순위를 비교하여 Work를 새로이 스케줄링 해야 하기 때문에(또는 기 예약된 Work의 이벤트가 더 우선순위가 높다면 무시) scheduleWork()를 매번 진행하게 됩니다. idle 상태에서 fiber에 expirationTime이 새겨져 있지 않다면? 이는 스케줄링된 Work가 없기 때문에 값이 같은지 비교하여 불필요한 Work가 예약되지 않도록 최적화 할 수 있습니다.

  2. 정리 섹션에서 1번의 답변은 추가 설명이 필요한 부분이 있습니다. 이와 관련된 내용은 본문에 추가하였습니다. 당시에 Sync, Async Work에 대한 내용은 제외하고 훅에 대한 내용만 작성하려다 보니 코드상으로만 설명하려다 살짝 오해의 소지가 있는 방향으로 내용이 작성되었습니다. action을 모아서 처리하는 내용은 react 18 톺아보기에 작성되어 있습니다.

괜한 시간을 디버깅에 소비시켜 죄송합니다 :(

happenask commented 11 months ago

@goidle 답변 고맙습니다. 항상 도움받고 있어 감사하게 생각합니다.