goidle / goidle.github.io

0 stars 0 forks source link

react/in-depth-react-reconciler_1/ #12

Open utterances-bot opened 10 months ago

utterances-bot commented 10 months ago

React 톺아보기 - 05. Reconciler_1 | Deep Dive Magic Code

모든 설명은 v16.12.0 버전 함수형 컴포넌트와 브라우저 환경을 기준으로 합니다. 버전에 따라 코드는 변경될 수 있으며 클래스 컴포넌트는 설명에서 제외됨을 알려 드립니다. 포스트별 주제 훅을 통해 컴포넌트 상태를 업데이트한다. 업데이트를 반영할 Work를 scheduler에게 전달하고 sch

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

happenask commented 10 months ago

root.lastExpirationTime은 이전 작업이 완료된 시간이 아니라 concurrent 모드에서 작업중 중단된 Work의 만료시간을 뜻하는건가요?

goidle commented 10 months ago

@happenask

concurrent 모드에서의 렌더링은 우선순위 기반으로 동작하기 때문에 기아현상이 발생할 수 있습니다. 이를 방지하기 위해 concurrent 렌더링이 너무 오래 걸린다면 만료를 시키고 sync 방식으로 렌더링을 진행시키는 것으로 주석에는 나와있습니다만, 16까지는 여전히 concurrent가 실험적 기능에 모드를 키지 않으면 모든 업데이트가 sync로 처리되는 상황이기 때문에 위 동작이 로직적으로 모두 풀어져 있지 않아 깊이 확인하지는 않았습니다.

18로 넘어가면서 expiraitonTime이 가졌던 concurrent와 만료의 개념이 분리되어, concurrent는 lane이 expirationTime은 말 그대로 만료의 개념을 가지게 됩니다. 위에서 설명한 상황도 로직에서 확인해볼 수 있습니다.

16 톺아보기에서는 concurrent와 expiraitonTime에 대한 내용은 깊이 들여다 볼 필요 없이 18로 넘어가면 더 자세한 설명을 확인할 수 있습니다.

Gi-Youn-Oh commented 7 months ago

VDOM에는 하나의 root만 존재합니다. 이 말인즉슨 root가 다르다는 의미는 ReactDOM.render()를 통해 만들어진 다른 VDOM에서 발생한 작업이 더 우선순위가 높다는 말입니다.

이 말씀에서 이해가 되지 않는 부분이 있어서 질문드립니다.

위 말씀은 workInProgress tree 가 여러개 라는 말씀이실까요?

virtual dom은 current와 workInProgress 두개로 double buffer형태로 진행되는 것으로 이해했는데 current와 workInProgress 상태에서 또 다른 workInProgress2 가 생기며 workInProgress 보다 workInProgress2가 우선순위가 높다 이 말씀이실까요?

잘못이해하고 넘어 온것 같아서 혼란이 오네요 ㅠㅠ

goidle commented 7 months ago

@Gi-Youn-Oh 우선순위와 관련된 부분은 expirationTime에서 lane 방식으로 변경되면서 달라진 부분이 많지만, root를 비교하는 부분은 이와 별개로 설명드릴 수 있어서 답변드립니다.

root는 ReactDOM.render(ReactElement, Container)를 통해서 생성되어 Container Element(HTML Element)에 저장됩니다. 다르게 말하면 ReactDOM.render를 통해 Container에 리액트 앱을 렌더링하면 해당 Container에는 하나의 root만 존재합니다. 이 root의 current에는 흔히 말하게 되는 In Memory Tree인 VDOM이 구성되어 있습니다. 이는 코드에서 current <- alternate -> workInProgress로 서로 참조되어 사용됩니다.

해당 문장에서 설명하는 조건은 root !== workInProgressRoot입니다. workInProgressRoot는 렌더링이 진행되고 있는 root을 가리키고, root는 현재 렌더링을 진행할 root을 가리키고 있습니다. 이 둘이 다르다는 말은 진행중이던 root(workInProgressRoot)보다 우선순위가 더 높은 root가 인입되었기 때문에 이전에 진행하던 렌더링 스택을 비우기 위해 prepareFreshStack를 호출한다는 의미입니다.

Gi-Youn-Oh commented 7 months ago

빠른답변 감사합니다!

혹시 그렇다면 우선순위가 더 높은 root가 인입되었다는 말씀이 새로운 Work(기존 workInProgress 보다 더 높은 우선순위)가 생기면 기존 workInProgress를 비우고 시작하기 위해 prepareFreshStack을 호출한다는 말씀이실까요?

아니면 ReactDOM.render 로 새로운 container와 root 가 생겨 인입되었다는 걸까요?

제가 이해도가 조금 부족한 것 같네요ㅠ

Gi-Youn-Oh commented 7 months ago

조금 더 헷갈리는 부분을 첨언하자면 예를들어 간단한 React앱에서 ReactDOM.render() 함수는 index.jsx 최상단에서 한번만 호출되어 (current와 alternate workInProgress) 1개로만 작업이 이루어진다고 생각하고 있는데,새로운 Root 가 유입된다는 것은 ReactDOM.render()가 또 실행되어 (current와 alternate workInProgress) 가 또 생성이 된다는 말인건가? 라는 생각이 들었습니다 ㅠ

goidle commented 7 months ago

@Gi-Youn-Oh 헷갈리시는 것을 짚어 보자면.. 일반적인 리액트 앱 개발에서는 root가 하나만 있을거예요. \<div id="foo" /> 당연히 ReactDOM.render도 하나의 Container(foo)을 대상으로만 호출될 것입니다.

하지만 다른 케이스에서는 여러 ReactDOM.render가 존재할 수 있습니다. 예를 들어 리액트로 마이그레이션 하는 프로젝트와 같이 UI의 특정 부분만 우선적으로 리액트를 적용할 수 있습니다. 이런 경우 ReactDOM.render가 여러 Container를 대상으로 호출될 수 있습니다. \<div id="foo" /> \<div id="bar" />, ReactDOM.render(\<Foo />, FooContainer) ReactDOM.render(\<Bar />, BarContainer)

여기서 ReactDOM 모듈은 하나입니다.(Webpack을 통해서 같은 모듈을 사용할 것이기에). ReactDOM은 이제 두개의 root를 처리해야 되는 상황이 발생할 수 있습니다. foo 앱을 렌더링 하다 bar의 렌더링이 우선순위가 더 높은 경우, foo는 그대로 두고 ReactDOM은 이전 작업에서 설정했던 여러 참조들을 정리합니다.

여기서 bar를 렌더링한다고 foo의 workInProgress tree를 순회하면서 정리할 필요는 없고, ReactDOM이 내부적으로 foo 렌더링을 하기 위해 사용했던 변수만 정리하면 됩니다. 이후에 다시 foo을 렌더링 할때 중지되었던 위치 부터 다시 시작할 수 있기 때문입니다. <- 요 부분은 내부 구현사항에 따라 언제든지 달라질 수 있습니다. 제가 여러 상황에 대해서 다 알고 있는 것은 아니라서. 다만 이 문단을 작성한 이유는 질문 주신 내용에서 기존 workInProgress를 비우고에서 기존 workInProgress가 foo가 참조하고 있는 tree가 아니라 그냥 ReactDOM이 내부적으로 정의한 workInProgress 변수임을 전달하기 위함입니다.

Gi-Youn-Oh commented 7 months ago

덕분에 정리가 되었습니다 감사합니다! 여러 ReactDOM이 여러 root를 생성했을 때 현재 렌더링해야 하는 root와 작업중인 wokInProgress (ReactDOM 모듈은 하나 이기 때문에) 일치 여부를 확인하고 작업하기 위한 준비를 한다는 것이 였군요. workInProgress 변수도 제가 체크하지 못했었네요 우문현답 감사합니다.. 🙏🏻