서론
Zustand를 사용한 상태 관리는 효율성과 성능을 향상시킬 수 있지만, 잘못 사용하면 예상치 못한 성능 저하로 이어질 수 있다.
문제진단
다음은 Zustand를 사용한 코드이다. 이 코드는 상태 a
와 b
를 관리하며, 각 상태에 대한 setter 함수를 포함한다.
이를 다음과 같이 사용할 때 개발자는 A 컴포넌트의 버튼을 눌렀을 때, A 컴포넌트만 리렌더하기를 바란다. 하지만 버튼을 누를 시 A, B 컴포넌트가 모두 리렌더된다.
여기서 A와 B 컴포넌트가 같이 리렌더되는 이유는 Zustand 스토어를 사용할 때 전체 객체를 호출하는 방식 때문이다. 이 방법은 스토어의 모든 상태를 리턴하기 때문에, 하나의 상태가 변경되어도 연관된 모든 컴포넌트가 리렌더된다.
최적화 전략
상태 선택적 사용
appStore
를 호출할 때 전체 상태를 가져오는 대신, 필요한 상태만 선택적으로 추출하는 방법을 사용하자. 이는 리렌더링을 해당 상태에 한정 시키는 효과를 가진다.
이제 기대했던 바와 같이 A 컴포넌트의 버튼을 눌렀을 때, A 컴포넌트만 리렌더된다.
이 방식은 문제상황의 방식과 어떠한 차이가 있을까?
appStore에서 들고온 값에 대한 차이가 있다. 이 방식은 Object가 아닌 원시타입의 변수와 절대 바뀌지 않는 action 함수를 가져오고, 문제상황의 방식은 다음과 같이 구조분해 할당을 했더라도 { b, setB }
사실 들고 온 값은 {a, b, setA, setB}
이다.
들고온 값이 Object이냐 원시타입이냐의 차이인데 이것과 리렌더링에는 어떠한 상관관계가 있을까? 기본적으로 zustand는 react처럼, state의 변경에 얕은 비교를 한다.
얕은비교(shallow comparison)는 참조 자료형의 경우 메모리 주소를 비교하기 때문에, 서로 다른 메모리주소에 같은 값이 저장되어 있어도 다르게 평가한다.
따라서 객체 내부의 값이 변경되어도 새로운 Object가 할당되지 않으면, 상태변화를 인지하지 못한다. 때문에 리렌더링을 위해서는 zustand의 set 함수를 통해 새로운 Object로 갈아 끼워야한다.
shallow 함수 사용
동등성을 비교하는 custom 함수를 만들어 인자로 넘겨주기
zustand set 함수 이해하기
zustand는 React의 useState와 유사하게 불변성을 유지하면서 상태를 업데이트하는 방식을 사용한다. 예를 들어, useState를 사용할 때는 다음과 같이 할 수 있다.
zustand에서는 이러한 패턴이 매우 흔하기 때문에, set 함수는 자동으로 병합하는 기능을 제공한다.
set 함수가 위와같이 작동 하면서 새로운 객체를 생성하고, 할당하기 때문에 a가 바뀌어도 b에서 참조하고 있던 메모리주소가 바뀌게되어 위의 문제상황과 같이 불필요한 리렌더링이 발생하게 된다.