- https://roseline.oopy.io/dev/react-advanced-deep-dive-into-diffing-and-reconciliation 내용을 따라가며 학습한다
- 조건부 렌더링 하는 경우
{
isCompany ? (
<Input id="company-tax-id" placeholder="Enter your company Tax ID" onChange={handleCompanyChange} />
) : (
<Input id="personal-tax-id" placeholder="Enter your personal Tax ID" onChange={handlePersonalChange} />
);
}
- 조건에 따라 props가 모두 바뀌었고 아예 다른 동작을 해야하도록 구현했다
- 하지만 isCompany라는 상태에 따라 새로 그려지리라 기대했던 텍스트 인풋에 입력된 내용은 사라지지 않고 유지되었다
- 새로운 컴포넌트로 마운트 되었다면 인풋도 초기화 되었어야하는데 말이다
- 리액트가 컴포넌트가 바뀌었다고 판단하는 조건
type이나 key가 바뀌었을때
- 위 예시에서는 type이 Input으로 동일하며 key가 존재하지 않기 때문에
- 동일한 컴포넌트의 props만 바뀌어서 리렌더링 되었다
- 따라서 인풋에 입력했던 내용은 그대로 유지되었다
- reconciliation에 의한 것으로
- type을 바꿀 수 없다면 위 예시에서는 key를 다르게 주어 새로 마운트 하도록 해야한다
- Virtual DOM 트리 비교
const Component = () => {
const [isCompany, setIsCompany] = useState(false);
return (
<div>
<Input id="id1" placeholder="placeholder1" />
{isCompany ? <Input id="id2" placeholder="placeholder2" /> : <TextPlaceholder />}
</div>
);
};
- Component 컴포넌트를 Virtual DOM 트리로 바꾸면
- 사용자 컴포넌트는 type에 함수 그대로 참조된다
{
type: 'div',
props: {
children: [
{
type: Input,
props: { id: "id1", placeholder: "placeholder1" }
},
{
type: TextPlaceholder,
},
]
}
}
- 이러한 형태인데 state가 바뀌면 Component 자체가 다시 렌더링을 시작하며 변경된 Virtual DOM 트리와 비교 과정을 통한다
{
type: 'div',
props: {
children: [
{
type: Input,
props: { id: "id1", placeholder: "placeholder1" }
},
{
type: Input,
props: { id: "id2", placeholder: "placeholder2" }
},
]
}
}
- 첫번째 type인 div 는 변하지 않았으므로 패스
- 그 아래 children의 첫번째 type도 Input 그대로 동일하므로 패스
- 하지만 두번째 type 이 TextPlaceholder에서 Input으로 바뀌었으므로 새로 마운트 하며
- children 자체도 바뀐게 되므로 div도 리렌더 된다
- 이런식으로 Component가 상태 변경에 의해 리렌더링 되는데
- key가 동일한데 위치가 바뀐 경우엔 어떻게 될까?
<>
<Checkbox />
{isCompany ? <Input key="company" id="company-tax-id" /> : null}
{!isCompany ? <Input key="person" id="person-tax-id1" /> : null}
{isCompany ? <Input key="person" id="person-tax-id2" /> : null}
</>
// isCompany = true
[
{ type: Checkbox },
{ type: Input, props: { id: 'company-tax-id' } },
null,
{ type: Input, props: { id: 'person-tax-id2' } }, // key=person
][
// isCompany = false
({ type: Checkbox },
null,
{ type: Input, props: { id: 'person-tax-id1' } }, // key=person 언마운트 후 마운트
null)
];
- 상태 변경에 따라 Checkbox의 type은 동일하기에 컴포넌트 유지된다
- key가 company인 Input 컴포넌트는 언마운트 되었다
- key가 person인 Input 컴포넌트는 새로 마운트 되었다
- DOM에서 위치만 바뀌었을뿐 key가 같아 동일한 컴포넌트로 유지되지 않을까 싶지만
- 배열이 아닌 일반 요소에서는 유지 되지 않고 새로 마운트 된다
- 만약 배열이였다면
// data가 [1,2,3] 에서 [2,1,3] 으로 바뀌었을때
<>
{data.map(item => <Input key={item} value={item} />}
<Input key="1" value={111} />
</>
// data = [1,2,3]
{
[
{ type: Input, props: { value: 1 } },
{ type: Input, props: { value: 2 } },
{ type: Input, props: { value: 3 } },
], // 배열 요소
{ type: Input, props: { value: 111 } }; // 일반 요소
}
// data = [2,1,3]
{
[
{ type: Input, props: { value: 2 } }, // 리렌더링
{ type: Input, props: { value: 1 } }, // 리렌더링
{ type: Input, props: { value: 3 } }, // 리렌더링 되지 않음
],
{ type: Input, props: { value: 111 } }; // 리렌더링 되지 않음
}
- 배열과 일반 요소의 트리는 아예 분리되어있으며
- 같은 배열 내에서 동일한 key의 컴포넌트가 위치만 바뀌었으므로
- Input 컴포넌트 언마운트 되지 않고 리렌더링 된다