잡다로그

[React.js] 특정 props 변경에 따른 state 변수 초기화 본문

Web/React

[React.js] 특정 props 변경에 따른 state 변수 초기화

날으는다람쥐 2024. 8. 10. 19:33

특정 props가 변경될 때 마다 state 변수를 초기화하기 위해서는 어떻게 해야 할까?


예제 상황:

const App: React.FC = () => {
  const item1: Info = {
    id: 0,
    name: '',
    date: '',
    content: [''],
  };
  // ...
  return(
    <div>
      <Card data={item1} />
      <Card data={item2} />
    </div>
  );
};

const Card: React.FC<OwnProps> = ({data, ... }) => {
  const [content, setContent] = useState(data.content);
  const [title, setTitle] = useState(data.name);
  // ...

Card 컴포넌트에서 객체인 data를 props로 받는다. content와 title은 prop에 의해 초기화된다.

 

이를 해결하기 위해서는,

(1) 각 컴포넌트를 전부 다른 위치에 렌더링하거나

(2) key를 이용해 각 컴포넌트의 state를 초기화해야 한다.

 

(1)의 방법은, Card 컴포넌트의 수가 많아지면 JSX에서 각각 렌더링하는 것이 번거로워질 수 있다.

<Card index={item1.id} data={item1} />
<Card index={item2.id} data={item2} />

따라서 위와 같이 key값을 주어 각 컴포넌트를 구분하면, 렌더링 시 마다 각 컴포넌트의 state값을 초기화시킬 수 있다.

const App: React.FC = () => {
  const [dataArr, setDataArr] = useState<Info[]>([]);
  // ...
  return(
    <div>
      dataArr.map((item, index) => (
          <Card key={index} data={item />
        ))
    </div>
  );
};

주의할 것은, 아래와 같이 map을 활용해서 여러 요소를 랜더링할 때 key값을 index로 주어서는 안된다는 것이다. 데이터에 따라 같은 index를 가지게 되어 결국 "같은 위치"로 인식되는 것과 동일해지기 때문이다.

왜 이렇게 동작할까?

React에서 화면의 각 컴포넌트는 완전히 분리된 state를 가진다. 각 Card 컴포넌트마다 독립된 content와 title state를 가지게 되는 것이다.

React는 같은 자리의 같은 컴포넌트는 state를 보존한다.

UI 트리에서 없어지거나, 다른 컴포넌트가 렌더링되지 않는 한 유지하는 것이다.

"같은 자리"는, 컴포넌트가 어느 부모 밑의 몇 번째 자식인지 등을 의미한다. 말그대로 UI 트리에서의 위치를 의미하는 것이다. 이는 같은 "주소"라고도 볼 수 있다. root의 첫 번재 자식으로 말이다.

 

밑의 코드처럼, prop인 isFancy의 값이 변경되어도, App의 첫 번째 자식으로 Counter 컴포넌트가 바뀌는 셈이다.  React의 관점에서는 같은 위치에 같은 컴포넌트이므로, 같은 Counter 컴포넌트라고 인식한다.

 

[❌제거되어 state 초기화 되는 경우]

위의 코드에서, 두 번째 Counter 컴포넌트가 showB state에 의해 보여지지 않게되면, UI 트리에서도 제거된다.  → state 값 초기화

[ ⭕ props 변경에도 state 초기화 안되는 경우]

밑의 코드에서, prop인 isFancy값에 따라 Counter 컴포넌트가 렌더링되지만, 언제나 root(<div>)의 첫 번째 자식이 되므로 같은 위치의 같은 컴포넌트로 인식하게 된다. → state 값 유지

반대로,

 

같은 자리의 다른 컴포넌트 타입으로 바꾸거나, 같은 위치에 다른 컴포넌트를 렌더링할 때 컴포넌트는 그의 전체 서브 트리의 state를 초기화한다. 따라서 경험상(rule of thumb) 리렌더링할 때 state를 유지하고 싶다면, 트리 구조가 "같아야" 한다.

같은 위치에서 state를 초기화하기 위해서는

1. 다른 위치에 컴포넌트를 렌더링하거나

2. key를 이용해 state를 초기화해야 한다.

 

같은 위치의 같은 컴포넌트이지만, 개념적으로 다른 컴포넌트일 때가 있다. UI에 같은 위치에 나타나지만, 날짜에 따라 일정들이 다르게 보이는 것처럼 말이다. 따라서 1번 해결방법대로 할 수 없는 경우 key값을 활용하게 된다.

 

https://ko.react.dev/learn/preserving-and-resetting-state#option-1-rendering-a-component-in-different-positions

✔️key를 이용해 state를 초기화하기

key를 이용하면 React에게 단지 첫 번째 컴포넌트나 두 번째 컴포넌트가 아니라 특정한 카운터라고 말해줄 수 있다. 트리 어디에서 나타나든 고유한 컴포넌트임을 알려줄 수 있는 것이다.

key를 명시하면 React는 부모 내에서의 순서 대신에 key 자체를 위치의 일부로 사용한다.

{isPlayerA ? (
  <Counter key="Taylor" person="Taylor" />
) : (
  <Counter key="Sarah" person="Sarah" />
)}

이 예제와 같이 JSX에서 "같은 위치"에 나타나더라도 다른 key값을 주게 되면, 컴포넌트마다 고유한 state를 가지게 되어 초기화가 가능해진다.

🙅🏻‍♀️좋지 않은 방법: useEffect 의존성에 props를 추가하고, 내부에서 set 실행

const Card: React.FC<OwnProps> = ({ data, ... }) => {
  const [newContent, setNewContent] = useState(data.content);
  const [newTitle, setNewTitle] = useState(data.name);

  useEffect(() => {
    setNewContent(data.content);
    setNewTitle(data.name);
  }, [data]);
  //...

특정 prop가 바뀔 때마다, state를 업데이트(값 변경) 하기 위해 useEffect에서 state를 변경하게 되면 컴포넌트의 렌더링이 끝난 후 상태 변경이 발생하여, 이로 인해 React가 컴포넌트를 다시 렌더링하게 되어 자식 컴포넌트가 두 번 렌더링될 수 있다.

따라서 전체 컴포넌트 트리의 state를 초기화하려면 컴포넌트에 다른key를 전달하는 방법 을 사용하는 것이 좋다. 


https://ko.react.dev/learn/preserving-and-resetting-state

 

State를 보존하고 초기화하기 – React

The library for web and native user interfaces

ko.react.dev

 

Comments