일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- sucoding
- Server State
- 스나이퍼팩토리
- 개발
- TypeScript
- 프로젝트캠프
- 프론트엔드
- 수코딩
- STATE
- 리액트프로젝트
- React
- 프론트엔드 개발
- 웅진씽크빅
- frontend
- 상태 관리 라이브러리
- 공식문서
- 유데미
- tanstack query
- React Query
- Today
- Total
yunicornlab
Zustand에서 Selector 사용 방법에 따른 차이 알아보기 본문
zustand에서 create 함수로 store를 생성한 후에, 이 생성한 store에서 상태를 가져오는 방법은 두 가지가 있다.
selector를 직접 정하느냐 마느냐!
이 둘의 차이점은 바로 리렌더링 문제에 있다.
// 방법 (1) : selector 지정 안하고 기본값인 (state) => state로 사용
// 즉, 전체 상태 구독
const store = useStore();
// 또는 const { count } = useStore();
// 방법 (2) : selector = (state) => state.count로 사용
// 즉, count만 구독
const count = useStore((state) => state.count); // count만 구독
전체 상태를 구독하는 방향이 되면, store의 상태값 중 하나의 상태만 값이 바뀌어도 컴포넌트가 리렌더링된다.
하지만 selector로 예를 들어 count라는 상태만 구독하게 되면, count 상태가 바뀔때만 컴포넌트가 리렌더링되고, 다른 컴포넌트에 의해 다른 상태가 바뀌어도 count를 구독한 컴포넌트는 리렌더링되지 않는다.
(리렌더링 조건 : 해당 상태를 "구독"한 컴포넌트)
정말 그런지 알아보기 위해 다음과 컴포넌트를 설계하고 코드를 작성했다.
첫 번째 테스트
useStore.js
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
text: '',
increaseCount: () => set((state) => ({ count: state.count + 1 })), // count 증가
updateText: (newText) => set({ text: newText }), // text 업데이트
}));
export default useStore;
App.jsx
import FullSubscriber from './FullSubscriber';
import CountSubscriber from './CountSubscriber';
import useStore from './useStore'; // Zustand 스토어 import
const App = () => {
const increaseCount = useStore((state) => state.increaseCount); // count 업데이트 액션
const updateText = useStore((state) => state.updateText); // text 업데이트 액션
return (
<>
<h1>Zustand Selector Test</h1>
<div>
<button onClick={increaseCount}>Increase Count</button>
<button onClick={() => updateText('Hello World!')}>Update Text</button>
</div>
<FullSubscriber />
<CountSubscriber />
</>
);
};
export default App;
FullSubscriber.jsx
import useStore from './useStore';
const FullSubscriber = () => {
const { count, text } = useStore(); // 전체 상태 구독
console.log('FullStateSubscriber'); // 렌더링 확인 로그
return (
<div>
<h2>Full State Subscriber</h2>
<p>Count: {count}</p>
<p>Text: {text}</p>
</div>
);
};
export default FullSubscriber;
CountSubscriber.jsx
import useStore from './useStore';
const CountSubscriber = () => {
const count = useStore((state) => state.count); // count만 구독
console.log('CountSubscriber'); // 렌더링 확인 로그
return (
<div>
<h2>Count Subscriber</h2>
<p>Count: {count}</p>
</div>
);
};
export default CountSubscriber;
UI 결과
동작 결과
- Increase Count 버튼을 누를 경우
- FullSubscriber 컴포넌트 리렌더링
- CountSubscriber 컴포넌트 리렌더링
- Update Text 버튼을 누를 경우
- FullSubscriber 컴포넌트 리렌더링
동작 결과 설명
- count 상태가 변경되면 count를 포함한 전체 상태를 구독한 FullSubscriber 컴포넌트와 count를 구독한 CountSubscriber 컴포넌트가 모두 리렌더링된다.
- 하지만, text 상태가 변경되면, text를 포함한 전체 상태를 구독한 FullSubscriber 컴포넌트는 리렌더링되지만,
count만을 구독한 CountSubscriber 컴포넌트는 리렌더링되지 않는다.
두 번째 테스트
첫 번째 테스트를 하다가 하나 더 궁금해졌는데 상태 자체를 구독하지 않고 상태를 변경하는 함수만 구독하는 경우는 어떻게 될까 궁금해졌다. 그래서 price라는 상태를 하나 더 추가하고 이 상태를 구독하는 코드는 두지 않은 채로 다시 작성해보았다.
FullSubscriber와 CountSubscriber 코드는 변경하지 않았기 때문에 동일하게 FullSubscriber는 count, text를 구독하고 CountSubscriber는 count만 구독한다.
useStore.js
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0, // 상태 1
text: '', // 상태 2
price: 0, // 상태 3
increaseCount: () => set((state) => ({ count: state.count + 1 })), // count 증가
updateText: (newText) => set({ text: newText }), // text 업데이트
increasePrice: () => set((state) => ({ price: state.price + 1 })), // price 증가
}));
export default useStore;
App.jsx
import FullSubscriber from './FullSubscriber';
import CountSubscriber from './CountSubscriber';
import useStore from './useStore'; // Zustand 스토어 import
const App = () => {
const increaseCount = useStore((state) => state.increaseCount); // count 업데이트 액션
const updateText = useStore((state) => state.updateText); // text 업데이트 액션
// 코드 추가
const increasePrice = useStore((state) => state.increasePrice); // count 업데이트 액션
return (
<div style={{padding: "50px"}}>
<h1>Zustand Selector Test</h1>
<div>
<button onClick={increaseCount}>Increase Count</button>
<button onClick={() => updateText('Hello World!')}>Update Text</button>
{/* 코드 추가 */}
<button onClick={increasePrice}>Increase Price</button>
</div>
<FullSubscriber />
<CountSubscriber />
</div>
);
};
export default App;
동작 결과
- Increase Count와 Update Text 버튼은 첫 번째 테스트와 동일한 결과 발생
- Increase Price 클릭시
- FullSubscriber 컴포넌트만 리렌더링됨
동작 결과 설명
Increase Price 버튼을 통해 price 상태가 변경은 되었지만, price를 포함한 전체 상태를 구독하는 FullSubscriber 컴포넌트만 리렌더링되고, 그 이외에 price를 구독하는 컴포넌트가 없기 때문에 더 이상 리렌더링되는 컴포넌트가 없게 된다.
주의할 점
zustand가 컴포넌트를 리렌더링 시키는 조건은 "상태 구독"이다.
실제 상태를 해당 컴포넌트에서 사용했는지는 중요하지 않다.
그러면 만약, CountSubscriber.jsx 파일에 price를 구독하는 코드만 추가로 삽입하고 price는 사용하지 않는다면 어떻게 될까?
CountSubscriber.jsx
import React from 'react';
import useStore from './useStore';
const CountSubscriber = () => {
const count = useStore((state) => state.count); // count 구독
// 코드 추가
const price = useStore((state) => state.price); // price 구독
console.log('CountSubscriber'); // 렌더링 확인 로그
return (
<div>
<h2>Count Subscriber</h2>
<p>Count: {count}</p>
</div>
);
};
export default CountSubscriber;
이렇게 둔 후에 Increase Price 버튼을 누르면 이제는 CountSubscriber가 리렌더링된다!
즉, 상태를 구독하는 useStore() 코드 여부에 따라서 리렌더링 여부가 결정이 되기 때문에 컴포넌트를 설계할 때 구독할 상태를 특정할 때 잘 생각해야겠다.
'Frontend > State' 카테고리의 다른 글
Zustand의 create 함수 알아보기 (2) - create와 useStore (0) | 2024.12.12 |
---|---|
Zustand의 create 함수 알아보기 (1) - set 함수 (0) | 2024.12.11 |
zustand가 뭐야 어떻게 써 (0) | 2024.12.11 |