프론트엔드에서 상태는 렌더하는데 있어서 영향을 미칠 수 있는 값을 뜻한다.
상태를 어떻게 관리하느냐에 따라 웹을 렌더하는데 영향을 미칠 수 있다. 프론트엔드 개발자에게 상태관리는 중요한 임무가 되었다.
리액트는 독립적인 컴포넌트 단위로 구성되어 있다. useState hook을 사용하여 하나의 컴포넌트에서 상태를 관리하고 props를 통해 부모-자식 간에 상태를 전파할 수 있다.
상태가 시작된 지점과 어떤 컴포넌트를 거쳐가는지, 모든 흐름을 이해하고 기억한다면 useState와 props를 사용하는데 무리가 없다. 하지만 프로젝트의 규모가 커짐에 따라 관리해야 할 상태의 개수는 늘어날 것이다.
그렇기 때문에 상태관리 툴을 사용해 효율적으로 상태를 관리할 필요가 있다.
상태관리를 위한 툴로는 Context API, Redux, React Query, Zustand 등이 널리 사용되고 있다.
오늘은 이 중 Zustand에 대해 알아볼 것이다.
Zustand란 상태라는 뜻을 가진 독일어이다.
단순화된 Flux 원리를 사용하는 작고 빠르며 확장 가능한 상태 관리 솔루션이다. Hooks에 기반해 편리한 API를 제공한다.
Zustand는 다음과 같은 장점을 지니고 있다.
이름 뜻도 쉽지만 사용방법 또한 매우 쉽다.
바닐라 자바스크립트를 기준으로 핵심 로직의 코드 줄 수가 약 42줄밖에 되지 않는다.
상태가 변경되면 불필요한 리렌더링을 일으키지 않는다.
보일러플레이트가 거의 없다.
보일러플레이트란 최소한의 변경으로 여러 곳에서 재사용되며 반복적으로 비슷한 형태를 띄는 양상을 말한다.
redux Devtools를 사용할 수 있어 디버깅에 용이하다.
사실 내가 Zustand를 사용해본 가장 큰 이유는 위의 장황한 내용 때문은 아니고
내가 최고 존경하는 꼬마술사님께서 추천해주셨기 때문에
npx create-react-app "프로젝트명"
// App.js
...
function App() {
const [memo, setMemo] = useState('');
const [memos, setMemos] = useState([]);
const handleWriteMemo = (e) => {
setMemo(e.target.value);
};
const handleAddMemo = (e) => {
e.preventDefault();
setMemos((prevMemos) => [...prevMemos, memo]);
setMemo('');
};
return (
<div>
<h1>메모 작성하기</h1>
<Form onAdd={handleWriteMemo} onSubmit={handleAddMemo} memo={memo} />
<Memos memos={memos} />
</div>
);
}
작성하는 메모 하나의 상태를 나타내는 memo
, 모든 메모 상태를 나타내는 memos
.
이러한 모든 상태는 App.js
에서 관리한다.
// ./components/Form.js
const Form = (props) => {
return (
<>
<form onSubmit={props.onSubmit}>
<input type='text' onChange={props.onAdd} value={props.memo} />
<button type='submit'>작성완료</button>
</form>
</>
);
};
Form.js
에서는 메모 하나를 입력하고 전송한다.
// ./components/Memos.js
const Memos = (props) => {
return (
<ul>
{props.memos.map((memo) => {
return <li key='memo'>{memo}</li>;
})}
</ul>
);
};
Memos.js
에서는 모든 메모를 리스트로 나열한다.
npm i zustand
터미널에 위 명령어를 입력하여 Zustand 라이브러리를 설치한다.
// ./stores/memos.js
import create from 'zustand';
const useMemosStore = create((set) => ({
memo: '',
setMemo: (text) => set({ memo: text }),
memos: [],
setMemos: (newMemo) =>
set((prev) => ({
memos: [...prev.memos, newMemo],
})),
}));
export default useMemosStore;
useState
로 관리했던 상태를 store로 옮겼다.
memo
와 memos
에는 초기값을 넣는다.
setMemo
와 setMemos
는 매개변수를 지정하고 어디에 어떻게 값을 넣을지 정한다.
setMemo
의 경우, text라는 값이 들어오면 memo
에 text를 넣는다.
setMemos
의 경우, 기존 memos
에 새로운 값인 newMemo를 추가해야 하므로 prev 매개변수를 활용한다.
이때 useState
와 다른 점은 스프레드 연산자를 사용할 때 prev.memos
와 같이 뒤에 타겟이 되는 배열명을 넣어야 하는 것이다.
// ./App.js
function App() {
return (
<div>
<h1>메모 작성하기</h1>
<Form />
<Memos />
</div>
);
}
더 이상 App.js
에서 상태를 관리할 필요가 없다.
기존의 useState
함수와 props는 모두 삭제한다.
// ./component/Form.js
import useMemosStore from '../stores/memos';
const Form = () => {
const { memo, setMemo, setMemos } = useMemosStore();
const handleWriteMemo = (e) => {
setMemo(e.target.value);
};
const handleAddMemo = (e) => {
e.preventDefault();
setMemos(memo);
setMemo('');
};
return (
<>
<form onSubmit={handleAddMemo}>
<input type='text' onChange={handleWriteMemo} value={memo} />
<button type='submit'>작성완료</button>
</form>
</>
);
};
// ./component/Memos.js
import useMemosStore from '../stores/memos';
const Memos = () => {
const { memos } = useMemosStore();
return (
<ul>
{memos.map((memo) => {
return <li key='memo'>{memo}</li>;
})}
</ul>
);
};
props로 상태를 내려받아 사용했던 컴포넌트에도 더이상 props가 필요 없다.
위와 같이 useMemosStore
함수만 받아서 사용하면 된다.
해당 컴포넌트 안에서 바로 상태를 변경할 수 있기 때문에 상태 변화를 더욱 직관적으로 볼 수 있다.
참고 링크
https://www.npmjs.com/package/zustand
https://velog.io/@ho2yahh/react-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-Zustand-%EB%A5%BC-%EC%95%84%EC%8B%AD%EB%8B%88%EA%B9%8C
어려웠는데 이해가 쏙쏙 돼요 감사해요!