안녕하세요! 프론트엔드 개발을 함께 공부하고 있는 강사입니다. Zustand 공식 문서를 통해 깊이 있게 학습하시려는 모습이 정말 멋집니다!
영어 문서라 처음엔 조금 막막하실 수 있지만, 제가 여러분이 이해하기 쉽도록 구어체로 전부 번역해 드릴게요. 원본의 내용은 하나도 빠짐없이 담았고, 실무에서 마주칠 수 있는 상황을 가정해서 제 경험을 녹인 부연 설명과 꿀팁도 팍팍 추가해 두었으니 천천히 따라와 주세요. 자, 그럼 시작해 볼까요?
useShallow를 사용하여 리렌더링 방지하기 (Prevent rerenders with useShallow)스토어에서 '계산된 상태(computed state)'를 구독(subscribe)해야 할 때, 가장 권장되는 방법은 선택자(selector)를 사용하는 거예요.
👨🏫 강사의 보충 설명:
여기서 '계산된 상태'라는 건, 스토어에 저장된 원본 데이터를 그대로 가져오는 게 아니라 컴포넌트에서 필요한 형태로 살짝 가공해서 가져오는 걸 의미해요. 예를 들어, 거대한 객체에서 특정 키(key)들만 뽑아 배열로 만들거나 특정 조건에 맞는 데이터만 필터링하는 경우죠.
이렇게 만들어진 계산된 선택자는, 출력된 결과값이 자바스크립트의 Object.is 기준으로 변경되었다고 판단될 때 컴포넌트의 리렌더링을 발생시킵니다.
👨🏫 강사의 보충 설명:
Object.is는 두 값이 완벽히 같은지 '엄격하게' 비교하는 메서드예요. 원시 타입(숫자, 문자열 등)일 때는 문제가 없지만, 선택자 함수가 새로운 배열([])이나 객체({})를 반환하도록 작성했다면 큰 문제가 생겨요. 내부의 데이터 값이 똑같더라도 배열이나 객체가 새로 생성되면 메모리 주소(참조값)가 달라지기 때문에, React는 "어? 값이 바뀌었네!" 하고 무조건 리렌더링을 시켜버립니다.
바로 이런 상황에서, 계산된 값이 이전 값과 항상 '얕은 비교(shallow equal)'를 통해 같다고 판단되면 리렌더링을 피하기 위해 useShallow를 사용해 볼 수 있어요.
👨🏫 강사의 보충 설명:
'얕은 비교(shallow equality)'란 객체나 배열의 가장 바깥쪽(첫 번째 깊이)에 있는 속성이나 요소들만 비교하는 것을 말해요. 안의 내용물이 정말로 바뀌었는지만 빠르게 검사해서, 불필요한 리렌더링을 막아주는 아주 강력한 도구랍니다.
우리가 곰(bear)들의 이름과 그들이 먹는 식사(meal)를 짝지어둔 스토어를 가지고 있고, 화면에는 이 곰들의 '이름'만 렌더링하고 싶다고 가정해 볼게요.
import { create } from 'zustand'
const useMeals = create(() => ({
papaBear: 'large porridge-pot',
mamaBear: 'middle-size porridge pot',
littleBear: 'A little, small, wee pot',
}))
export const BearNames = () => {
const names = useMeals((state) => Object.keys(state))
return <div>{names.join(', ')}</div>
}
그런데 이제 아빠 곰(papa bear)이 마음이 바뀌어서 죽(porridge) 대신 피자를 먹고 싶다고 하네요! 메뉴를 바꿔봅시다.
useMeals.setState({
papaBear: 'a large pizza',
})
이러한 변경 사항은 BearNames 컴포넌트의 리렌더링을 유발합니다. 얕은 비교(shallow equal) 기준으로 보았을 때 names의 실제 출력 결과(배열의 내용물)는 전혀 변하지 않았음에도 불구하고 말이죠!
👨🏫 강사의 보충 설명:
아빠 곰의 식사 메뉴가 바뀌었지만, 스토어에 있는 객체의 키(key) 자체인['papaBear', 'mamaBear', 'littleBear']는 전혀 변하지 않았죠? 하지만Object.keys(state)는 호출될 때마다 새로운 참조(주소)를 가진 배열을 반환하기 때문에 불필요하게 컴포넌트가 다시 그려지게 됩니다.
우리는 useShallow를 사용해서 이 문제를 고칠 수 있어요!
import { create } from 'zustand'
import { useShallow } from 'zustand/react/shallow'
const useMeals = create(() => ({
papaBear: 'large porridge-pot',
mamaBear: 'middle-size porridge pot',
littleBear: 'A little, small, wee pot',
}))
export const BearNames = () => {
const names = useMeals(useShallow((state) => Object.keys(state)))
return <div>{names.join(', ')}</div>
}
이제 곰들이 다른 식사 메뉴를 자유롭게 주문하더라도, 우리의 BearNames 컴포넌트에서는 불필요한 리렌더링이 발생하지 않습니다.
💡 강사님의 실무 팁!
실무에서 Zustand를 사용하실 때,state에서 원시 타입(string, number, boolean) 하나만 딱 빼오는 게 아니라면, 객체나 배열을 반환하는 선택자에는 무조건useShallow로 감싸주는 습관을 들이는 것을 적극 권장합니다.
초보 개발자분들이 Zustand를 쓰면서 "왜 이렇게 렌더링이 자주 일어나지?" 하고 당황하시는 이유의 90%가 바로 이 부분 때문이거든요. 데이터가 크고 복잡한 대시보드나 리스트 뷰를 만들 때 이 차이가 서비스의 퍼포먼스를 크게 좌우합니다!
이전 (Previous)
Map and Set Usage
다음 (Next)
Connect to state with URL