goidle / goidle.github.io

0 stars 0 forks source link

react/in-depth-react-reconciler_3/ #19

Open utterances-bot opened 2 months ago

utterances-bot commented 2 months ago

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

모든 설명은 v16.12.0 버전 함수형 컴포넌트와 브라우저 환경을 기준으로 합니다. 버전에 따라 코드는 변경될 수 있으며 클래스 컴포넌트는 설명에서 제외됨을 알려 드립니다. 6 - 6 reconcileChildren() 재조정 작업의 목적은 VDOM…

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

Gi-Youn-Oh commented 2 months ago

안녕하세요! reconcileChildrenArray() 함수에 대해 나름의 해석을 해보았는데 궁금한 점이 있어 질문 드립니다!

첫 for문에서는 current와 newChildren 순서대로 확인하며, key값이 다르면 무조건 null을 반환하고 for문 break 즉 current와 비교했을 때 이동이 발생했다면 첫 번째의 for문에서 처리는 pass → 두 번째 for문에서 updateFromMap()에서 key값을 기준으로(없다면 index를 기준으로) 찾고 여기서도 없다면 Fiber생성

한다고 해석을 해보았습니다.

그렇다면 key가 null이고 type이 같을 때 props만 변경되는 문제 외에 key값을 설정하지 않거나 index를 사용했을 때 흔히들 말하는 문제점은 구체적으로 어떤 문제가 있을까 생각을 해보았을때

다음과 같은 경우를 생각해보았는데 어떻게 생각하시는지 궁금합니다!

Case1. index를 기반으로 key값을 설정하면 위치가 변경될 경우 새로운 요소를 무조건 생성한다?

  1. index기반의 key → 위치가변경되면 key값도 변경 [’a’, ‘b’, ‘c’] ⇒ [’a’, ‘c’, ‘b’] 일때 key값은 [0, 1, 2] ⇒ [0, 2, 1]

    1. ‘a’요소는 동일하기 때문에 첫 for문에서 처리 이후 ‘b’ → ‘c’는 key가 다르기 때문에 패스
    2. 두번째 for문에서 key값이 일치하는 요소를 찾고 type이 같다면 current재활용 다르다면 생성
  2. 이 때 만약 고유 key값을 사용했다면 ?

  3. 위치변경이 발생했으므로 마찬가지로 첫 for문은 패스

  4. 마찬가지 type이 같다면 current재활용 다르다면 생성

⇒ 그렇다면 무슨 차이가?

Case2. [’a’, ‘b’, ‘c’, ‘d’] ⇒ [’a’, ‘b’, ‘z’, ‘c’, ‘d’] 이고 각 key값이 모두 null이라면 current의 ‘c’, ‘d’를 ‘z’, ‘c’로 업데이트하고 ‘d’를 새로생성 하여 추가 하지만 고유 키값이면 ‘z’만을 생성하여 삽입한다?

  1. key값이 모두 null
    1. key값은 null로 같고 type도 때문에 첫번째 for문에서 current 의 ‘c’→ ‘z’로 update, ‘d’ → ‘c’로 업데이트 후 첫 for문 종료
    2. ‘b’에 대해서 추가 생성 후 끝
  2. 이때 만약 고유 key 값이 있다면?
    1. ‘a’, ‘b’까지는 current의 위치 이동이 없으므로 첫번째 for문에서 처리, 이후 ‘z’에서 찾는 key값이 없기 때문에 newFiber = null을 반환할 것이고 for문 종료
    2. 두번 째 for문에서 current에 일치하는 key값이 없으므로 ‘z’를 새로 생성
    3. 나머지 ‘c’, ‘d’, 에 대해서 일치하는 key값을 찾아서 current재활용

⇒ 그렇다면 결국 current 의 ‘c’→ ‘z’로 update, ‘d’ → ‘c’로 업데이트하는 낭비가 발생하는 차이점이 끝인가?

위와 같은 의문점이 들었는데 답변 주시면 너무 감사드리겠습니다 🙏🏻

Gi-Youn-Oh commented 2 months ago

조금 더 질문에 대한 부연 설명을 하자면,

흔히들 말하는

  1. index로 key값을 사용하고 위치가 변경되었을때의 문제점에서 (위치변경되면 모든 요소를 새로운 것으로 간주하고 다시 생성한다?)

    1) index가 변경되어도 동일한 key값을 가진 current는 존재할 것이고 그렇다면 updateFromMap()에서 동일한 key값을 가진 요소가 current에 존재할 것이기 때문에, type도 같다면 props만 변경되지 않나? 라는 생각과 2) 결국 고유 key값을 사용하더라도 새로운 배열의 처음부터 끝까지 update or create를 하기위해 비교 check하는 것은 필수적이지 않은가? 라는 것이 두번째 생각이었습니다!

  2. 고유 key값을 사용하면 중간에 새로운 요소가 추가되었을 때 해당 요소를 생성하여 추가만 한다. 라는 말에서

    1) 새로운 요소를 생성하는 것은 동일하고, 추가된 요소의 뒤에있는 요소들의 위치는 한칸씩 밀려나야하므로 결국 update or create하기 위해서 비교check를 해야한다. 그러므로 새로운 요소만 삽입되는 것이 아니라 나머지 요소에 대해서 처음부터 끝까지 check는 해야하고 그렇다면 크게 차이가 있을까? 라는 생각이 들었습니다.

goidle commented 2 months ago

@Gi-Youn-Oh 첫 번째로 말씀 주신 가정의 “배열의 인덱스를 key로 사용했을 때”의 동작은 잘못되었습니다. 인덱스 기반이면 요소의 위치가 변경되어도 키의 위치는 변경되지 않습니다. 즉,  ['a', 'b', 'c'] ⇒ ['a', 'c', 'b'] 일 때 키 값은 여전히 [0, 1, 2 ]입니다.

[‘a’, ‘b’, ‘c’].map((name, index) => <div key={index} name={name} />); 
=>
[‘a’, ‘c’, ‘b’].map((name, index) => <div key={index} name={name} />); 

위 내용을 가지고 다시 로직을 생각해보시면 질문에 대답이 될 것 같습니다.

재조정 작업에서 불필요한 연산이 발생할 수 있는 케이스에 대해서 우리는 전혀 신경 쓰거나 고민할 필요는 없고, 실질적으로 집중해야하는 것은 사용자 기대와 다른 ui입니다.

이 관점에서 코드를 보고 우리가 이해해야 하는 부분은 배열의 인덱스를 key로 사용했을 경우에 기대와 다른 UI 동작이 발생하게 되는 이유가 무엇인지 파악하는 것입니다. 이에 대한 설명을 “key의 중요성” 섹션의 샘플 프로젝트로 작성했었는데 지금 보니 깨졌네요. 고치도록 하겠습니다. 고쳤습니다.

간략하게 요약하면 key와 type은 같은데 실질적으로 요소가 이동한 경우, props는 부모로부터 받은 그대로 사용하기 때문에 이동한 대로 UI가 그려질 수 있지만, Fiber 내부적으로 관리하는 이벤트와 상태는 위치가 이동하지 않았으므로 기대와 다른 UI가 그려질 수 있게 됩니다.

Gi-Youn-Oh commented 2 months ago

답변 감사합니다!