React에서 상태(state)는 컴포넌트의 라이프사이클 동안 정보를 유지하는데 사용되는 중요한 개념입니다. useState
훅을 이용하여 컴포넌트의 상태를 관리하면, 상태가 변경될 때마다 컴포넌트가 다시 렌더링 됩니다. 그런데, 상황에 따라서 컴포넌트의 상태가 유지되거나 초기화되는 경우가 있는데, 이는 컴포넌트가 렌더링 되는 방식에 따라 달라집니다.
삼항 연산자(? :
)와 논리 연산자(&&
)를 이용하여 컴포넌트를 렌더링 하는 경우, 두 연산자가 UI 트리(가상 DOM)에 미치는 영향은 서로 다릅니다. 이로 인해 React의 diffing 알고리즘이 컴포넌트를 다르게 인식하고 처리하며, 결론적으로 컴포넌트의 상태 유지에서 차이가 나게 됩니다.
삼항 연산자와 컴포넌트의 상태
삼항 연산자를 사용하면, 두 표현식 중 하나만 렌더링 되지만, 이 둘은 동일한 위치에 렌더링 되기 때문에, React는 이들을 같은 컴포넌트로 인식합니다. 따라서, 삼항 연산자를 사용하여 렌더링 된 컴포넌트의 상태는 유지됩니다.
예를 들어, 다음과 같은 코드에서는 Counter
컴포넌트의 상태가 유지됩니다.
import { useState } from 'react';
export default function Scoreboard() {
const [isPlayerA, setIsPlayerA] = useState(true);
return (
<div>
{isPlayerA ? (
<Counter person="Taylor" />
) : (
<Counter person="Sarah" />
)}
<button onClick={() => {
setIsPlayerA(!isPlayerA);
}}>
Next player!
</button>
</div>
);
}
function Counter({ person }) {
const [score, setScore] = useState(0);
const [hover, setHover] = useState(false);
let className = 'counter';
if (hover) {
className += ' hover';
}
return (
<div
className={className}
onPointerEnter={() => setHover(true)}
onPointerLeave={() => setHover(false)}
>
<h1>{person}'s score: {score}</h1>
<button onClick={() => setScore(score + 1)}>
Add one
</button>
</div>
);
}
https://codesandbox.io/s/suspicious-river-t9gcp8?file=/App.js
suspicious-river-t9gcp8 - CodeSandbox
suspicious-river-t9gcp8 by boonke using react, react-dom, react-scripts
codesandbox.io
위 코드에서 isPlayerA 값에 따라서 다른 person prop을 받지만, 실제로는 동일한 Counter 컴포넌트로 인식되므로 상태도 유지됩니다.
논리 연산자와 컴포넌트의 상태
논리 연산자를 사용하면, 조건이 참일 때만 표현식이 렌더링 됩니다. 따라서 조건이 바뀔 때마다 표현식은 새로운 위치에 렌더링 되는 것으로 React에게 인식됩니다. 이로 인해 컴포넌트가 언마운트되고 새로운 컴포넌트가 마운트 되는 것처럼 보입니다. 따라서, 논리 연산자를 사용하여 렌더링 된 컴포넌트의 상태는 초기화됩니다.
다음과 같은 코드에서는 Counter
컴포넌트의 상태가 초기화됩니다.
import { useState } from 'react';
export default function Scoreboard() {
const [isPlayerA, setIsPlayerA] = useState(true);
return (
<div>
{isPlayerA &&
<Counter person="Taylor" />
}
{!isPlayerA &&
<Counter person="Sarah" />
}
<button onClick={() => {
setIsPlayerA(!isPlayerA);
}}>
Next player!
</button>
</div>
);
}
function Counter({ person }) {
const [score, setScore] = useState(0);
const [hover, setHover] = useState(false);
let className = 'counter';
if (hover) {
className += ' hover';
}
return (
<div
className={className}
onPointerEnter={() => setHover(true)}
onPointerLeave={() => setHover(false)}
>
<h1>{person}'s score: {score}</h1>
<button onClick={() => setScore(score + 1)}>
Add one
</button>
</div>
);
}
https://codesandbox.io/s/modest-rgb-32sti1?file=/App.js
modest-rgb-32sti1 - CodeSandbox
modest-rgb-32sti1 by boonke using react, react-dom, react-scripts
codesandbox.io
위 코드에서 isPlayerA 값에 따라 다른 Counter 컴포넌트가 렌더링 되지만, 두 Counter 컴포넌트는 서로 다른 위치에 렌더링 되므로 각각 별도의 인스턴스로 처리되어 상태가 초기화됩니다.
결론
React는 JSX 마크업에서가 아닌 UI 트리에서의 위치에 관심이 있다는 것을 기억하고 연산자에 따라서 위치를 다르게 인식할 수 도 있다는 점을 주의해서 적절히 활용하자!
레퍼런스