-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: React State Management โ using Zustand
- Loading branch information
Showing
1 changed file
with
385 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,385 @@ | ||
## ๐ [React State Management โ using Zustand](https://medium.com/globant/react-state-management-b0c81e0cbbf3) | ||
|
||
### ๐๏ธ ๋ฒ์ญ ๋ ์ง: 2024.08.19 | ||
|
||
### ๐ง ๋ฒ์ญํ ํฌ๋ฃจ: ์ํ(์ต์์ฐ) | ||
|
||
--- | ||
|
||
<img width="500px" src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*y_e4OOiSszPMpyjUD9_BOw.png"/> | ||
|
||
๋น์ ์ ์ํ(state)์ ์ํ ๊ด๋ฆฌ(state management)๊ฐ React ์ ํ๋ฆฌ์ผ์ด์ ์ ์ค์ํ ๋ถ๋ถ์ด๋ผ๊ณ ์๊ฐํ์๋์? ์ํ ๊ด๋ฆฌ๋ฅผ ์ด๋ ค์ํด๋ณธ ์ ์ด ์๊ฑฐ๋ ๊ฐ๋จํ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐพ๊ณ ์์๋ค๋ฉด, ์ด ๊ธ์ด ๋น์ ์ ์ํ ๊ฒ์ ๋๋ค. ์ด ๊ธ์์๋ ๊ฐ๋จํ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ Zustand๋ฅผ ์ฌ์ฉํ์ฌ React ์ํ๋ฅผ ์ด๋ป๊ฒ ๋ค๋ฃฐ ์ ์๋์ง ๋ณด์ฌ์ค ๊ฒ์ ๋๋ค. | ||
|
||
์ ๋ง๋ก! ์ํ์ ์ํ ๊ด๋ฆฌ๋ ํญ์ React ์ ํ๋ฆฌ์ผ์ด์ ์ ์ค์ํ ์ธก๋ฉด์ด์์ต๋๋ค. React๋ ์ํ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ๋ค์ ๋ ๋๋งํ๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ํ๋ ์ปดํฌ๋ํธ์ ๋ํ ๋ฐ์ดํฐ๋ ์ ๋ณด๋ฅผ ํฌํจํ ์ ์์ต๋๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ฑ์ฅํจ์ ๋ฐ๋ผ ์ํ์ ์ปดํฌ๋ํธ ๊ฐ์ ๋ฐ์ดํฐ ํ๋ฆ์ ๊ด๋ฆฌํ๋ ๊ฒ์ด ๋งค์ฐ ์ค์ํด์ง๋๋ค. | ||
|
||
### React ์ํ ๊ด๋ฆฌ | ||
|
||
React ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์๋ ์ฌ๋ฌ ๊ฐ์ง๊ฐ ์์ต๋๋ค. | ||
|
||
1. **React ๋ค์ดํฐ๋ธ ์ํ ๊ด๋ฆฌ.** useState, useReducer, useRef, useContext์ ๊ฐ์ ํ (hooks)๊ณผ ์ฌ์ฉ์ ์ ์ ํ (custom hooks)์ ๋ค์ดํฐ๋ธ ์ํ ๊ด๋ฆฌ๋ฅผ ์ง์ํฉ๋๋ค. | ||
2. **๊ฐ์ ์ํ ๊ด๋ฆฌ์.** React Router์ React Query์ ๊ฐ์ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด ์์ต๋๋ค. ์ด๋ค์ ์ฃผ๋ก ์ํ ๊ด๋ฆฌ๋ฅผ ์ํด ์ฌ์ฉ๋์ง ์์ง๋ง, ๋ค์ดํฐ๋ธ ํ ๊ณผ ๊ฒฐํฉํ๋ฉด ์ํ๋ฅผ ์ ๊ด๋ฆฌํ ์ ์์ต๋๋ค. | ||
3. **์ง์ ์ํ ๊ด๋ฆฌ์.** ์ํ ๊ด๋ฆฌ๋ฅผ ์ํด์๋ง ์ฌ์ฉ๋๋ ์๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์์ต๋๋ค. Redux, Zustand, Jotai, Valtio๊ฐ ์ด ๋ฒ์ฃผ์ ์ํฉ๋๋ค. | ||
|
||
### Zustand๋ ๋ฌด์์ธ๊ฐ์? | ||
|
||
Zustand๋ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค. ์๊ณ ๋น ๋ฅด๋ฉฐ ํ์ฅ์ฑ์ด ์์ต๋๋ค. ์ด๋ ๊ฐ๋จํ ์์คํ ์ผ๋ก, ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋๊ฐ ๊ฑฐ์ ์์ต๋๋ค. Redux ๋ฐ ์ ์ฌํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ณด๋ค ์ ์ ์ฝ๋๋ก React ์ํ๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค. Zustand๋ provider์ ์์กดํ์ง ์๊ธฐ ๋๋ฌธ์, React ๋ก์ง์ ๋ ์์ฑํด๋ ๋๋ฉฐ, ์ฐ๋ฆฌ๊ฐ ์ข ์ข ์๊ธฐ ์ฌ์ด ๋ถ๋ถ๋ค์ ์ค์ผ ์ ์์ต๋๋ค. ์ด๋ ๊ฐ์ํ๋ flux ์์น์ ๊ธฐ๋ฐ์ผ๋ก ์๋ํ๋ฉฐ, ์ฃผ๋ก Hook์ ์ฌ์ฉํฉ๋๋ค. | ||
|
||
์ Zustand๋ฅผ ์ ํํด์ผ ํ ๊น์? | ||
|
||
- Zustand๋ context๋ณด๋ค ๋น ๋ฆ ๋๋ค. ํน์ ์ํ๋ฅผ ์ ํํ ์ ์๋ ์ต์ ์ ์ ๊ณตํฉ๋๋ค. | ||
- ์ํ ๋ณํฉ์ด ๊ธฐ๋ณธ์ ์ผ๋ก ์ง์๋ฉ๋๋ค. ๊ฐ์ฒด ์ํ `{x:1, y:2}`์ ๋จ์ผ ์์ฑ์ ์ ๋ฐ์ดํธํ๋ค๊ณ ๊ฐ์ ํด๋ด ์๋ค. `{y:3}`์ผ๋ก ์ง์ ์ค์ ํ ์ ์์ต๋๋ค. Zustand๋ ๋ฐ์ดํฐ๋ฅผ ์๋์ผ๋ก ๋ณํฉํด ์ค๋๋ค. ๊ธฐ์กด ์ํ๋ฅผ ๋ถ๋ฐฐํ๊ณ `{โฆstate, y:3}`์ฒ๋ผ ์์ฑ์ ์ ๋ฐ์ดํธํ ํ์๊ฐ ์์ต๋๋ค. | ||
- Zustand๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํ์ฅ ๊ฐ๋ฅํฉ๋๋ค. ๋ฐ๋ผ์ ๋ค์ํ ๋ฏธ๋ค์จ์ด ์ ํ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. | ||
- Zustand๋ ํน์ ๋ฐฉ์์ ์ฝ๋งค์ด์ง ์์ต๋๋ค. ๊ถ์ฅ๋๋ ์ ๊ทผ ๋ฐฉ์์ด ์๋๋ผ๋, ๊ทธ๊ฒ์ ๋ฐ๋์ ์ฑํํ ํ์๋ ์์ต๋๋ค. | ||
|
||
### Zustand vs. Redux | ||
|
||
์์๋ค์ํผ, Redux๋ React์ ํจ๊ป ์๋ํ๋ ๊ณ ์ ์ ์ธ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค. ์ด๋ React ์ํ ๊ด๋ฆฌ๋ฅผ ์ํ ๊ฐ์ฅ ์ธ๊ธฐ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๊ฐ์ฃผ๋ฉ๋๋ค. ๋ฐ๋ผ์ ์ด ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ํคํ ์ฒ๋ฅผ ๋น๊ตํ๋ ๊ฒ์ด ์ด ๊ธ์ ์ ํฉํ ์์๊ฐ ๋ฉ๋๋ค. ์๋์ ์ํคํ ์ฒ๋ฅผ ์ดํด๋ณด๋ฉด์ Redux๊ฐ ์ด๋ป๊ฒ ์๋ํ๋์ง ํ์ธํด๋ณด์ธ์. | ||
|
||
<img width="500px" src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*GUUEnrvJJTkKSx-XXdvW_w.png"/> | ||
|
||
๋จผ์ , ์์ ์ค๋ช ๋ ์ํคํ ์ฒ์์ ๋ณผ ์ ์๋ฏ์ด ํ๋ก ํธ์๋ ์ฌ์ฉ์ ์ธํฐํ์ด์ค๊ฐ ์์ต๋๋ค. action creators๋ ๊ฐ ์ฌ์ฉ์ ์์ฒญ์ ๋ํด ์ฌ๋ฐ๋ฅธ ์ก์ ์ด ํธ๋ฆฌ๊ฑฐ๋๋๋ก ๋ณด์ฅํฉ๋๋ค. ์ก์ ์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ด๋ค ์ผ์ด ๋ฐ์ํ๋์ง๋ฅผ ์ค๋ช ํ๋ ์ด๋ฒคํธ๋ผ๊ณ ์๊ฐํ ์ ์์ต๋๋ค. ๋ฒํผ ํด๋ฆญ์ด๋ ๊ฒ์ ์ํ ๊ฐ์ ๊ฒ์ด ์ด์ ํด๋นํ ์ ์์ต๋๋ค. dispatchers๋ ์ด๋ฌํ ์ก์ ์ store๋ก ๋ณด๋ด๋ ๋ฐ ๋์์ ์ค๋๋ค. ์ดํ reducers๋ ์ํ๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง ๊ฒฐ์ ํฉ๋๋ค. reducer ํจ์๋ ํ์ฌ ์ํ์ ์ก์ ๊ฐ์ฒด๋ฅผ ๋ฐ์ ์ํ๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค. ํ์์ ๋ฐ๋ผ ์๋ก์ด ์ํ๋ฅผ ๋ฐํํ๋ฉฐ, ์ ๋ฐ์ดํธ๋ ์ํ ์์ ์ฌํญ์ด UI๋ฅผ ๋ ๋๋งํ๊ฒ ๋ฉ๋๋ค. | ||
|
||
์ด์ ํฅ๋ฏธ๋ก์ด ๋ถ๋ถ์ด ๋ค๊ฐ์ต๋๋ค! ์๋์ ์ ๊ณต๋ ์ํคํ ์ฒ๋ฅผ ํตํด Zustand๊ฐ ์ด๋ป๊ฒ ์๋ํ๋์ง ์ดํด๋ณด์ธ์. ์ด ๋จ์ํ๋ ๋ค์ด์ด๊ทธ๋จ์ ํฅ๋ฏธ๋ฅผ ๋๋ ์๋ ์์ต๋๋ค. | ||
|
||
<img width="500px" src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*K5SdovTtjwHe545H6Q2D_A.png"/> | ||
|
||
์ฌ๊ธฐ์์๋ UI ์ปดํฌ๋ํธ๊ฐ ์์ต๋๋ค. ๋ณ๊ฒฝ ์์ฒญ์ด ๋ค์ด์ค๋ฉด ๊ทธ๊ฒ์ store๋ก ๋ผ์ฐํ ๋ฉ๋๋ค. store๋ ์ํ๊ฐ ์ด๋ป๊ฒ ๋ณ๊ฒฝ๋์ด์ผ ํ๋์ง๋ฅผ ๊ฒฐ์ ํฉ๋๋ค. store๊ฐ ์๋ก์ด ์ํ๋ฅผ ๋ฐํํ๋ฉด, UI๋ ์ ๋ฐ์ดํธ๋ ๋ณ๊ฒฝ ์ฌํญ๊ณผ ํจ๊ป ๋ ๋๋ง๋ฉ๋๋ค. ์ฌ๊ธฐ์์๋ action creator, dispatcher, reducer๊ฐ ๋ณด์ด์ง ์์ต๋๋ค. ๋์ , Zustand๋ ์ํ ๋ณ๊ฒฝ์ ๊ตฌ๋ ํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ์ด๋ฅผ ํตํด UI๊ฐ ๋ฐ์ดํฐ์ ๋๊ธฐํ ์ํ๋ฅผ ์ ์งํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค. | ||
|
||
> ๋ ํฌ๊ณ ๋ณต์กํ ํดํท์ธ Redux์ ๋ณต์ก์ฑ์ ํผํ๋ฉด์๋ ๊ฐ๋จํ๊ณ ๊ฐ๋ฒผ์ด ์ํ ๊ด๋ฆฌ ์๋ฃจ์ ์ ์ฐพ๊ณ ์๋ ๊ฐ๋ฐ์๋ค์๊ฒ Zustand๋ ์๋ฒฝํ ์ ํ์ ๋๋ค. | ||
์ด๋ก ์ ์ฌ๊ธฐ์ ๋์ ๋๋ค. ์ด์ ์ค์ต์ ์์ํด๋ณด์ธ์! | ||
|
||
### ReactJs์์ Zustand ์ฌ์ฉ ๋ฐฉ๋ฒ | ||
|
||
Zustand๋ฅผ ์ฌ์ฉํ์ฌ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด React ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด ๋ด ์๋ค. ๋์ ๋ฐํ ๋ฐ ๋ฐ๋ฉ์ ์ถ์ ํ๋ Library Store ์ ํ๋ฆฌ์ผ์ด์ ์ ์๋ก ๋ค์ด๋ณด๊ฒ ์ต๋๋ค. ๋จ๊ณ๋ ์๋์ ๊ฐ์ต๋๋ค. | ||
|
||
#### 1. React ์ ํ๋ฆฌ์ผ์ด์ ์์ฑ | ||
|
||
์๋ ๋ช ๋ น์ ์ฌ์ฉํ์ฌ React ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ฑํฉ๋๋ค. | ||
|
||
```bash | ||
npx create-react-app project_name | ||
``` | ||
|
||
#### 2. Zustand ์์กด์ฑ ์ค์น | ||
|
||
ํ๋ก์ ํธ ๋๋ ํ ๋ฆฌ๋ก ์ด๋ํ์ฌ React ์ํ ๊ด๋ฆฌ๋ฅผ ์ํด Zustand ์์กด์ฑ์ ์ค์นํฉ๋๋ค. | ||
|
||
```bash | ||
npm i zustand | ||
``` | ||
|
||
#### 3. store ์์ฑ | ||
|
||
bookStore.js ํ์ผ์ ์์ฑํ๊ธฐ ์ํด ์๋ ์ฝ๋๋ฅผ ์ฐธ๊ณ ํ์ฌ ๋์ store๋ฅผ ๋ง๋ญ๋๋ค. | ||
|
||
```javascript | ||
import { create } from 'zustand'; | ||
|
||
const bookStore = (set, get) => ({ | ||
books: [], | ||
noOfAvailable: 0, | ||
noOfIssued: 0, | ||
addBook: (book) => { | ||
set((state) => ({ | ||
books: [...state.books, { ...book, status: 'available' }], | ||
noOfAvailable: state.noOfAvailable + 1, | ||
})); | ||
}, | ||
issueBook: (id) => { | ||
const books = get().books; | ||
const updatedBooks = books?.map((book) => { | ||
if (book.id === id) { | ||
return { | ||
...book, | ||
status: 'issued', | ||
}; | ||
} else { | ||
return book; | ||
} | ||
}); | ||
set((state) => ({ | ||
books: updatedBooks, | ||
noOfAvailable: state.noOfAvailable - 1, | ||
noOfIssued: state.noOfIssued + 1, | ||
})); | ||
}, | ||
returnBook: (id) => { | ||
const books = get().books; | ||
const updatedBooks = books?.map((book) => { | ||
if (book.id === id) { | ||
return { | ||
...book, | ||
status: 'available', | ||
}; | ||
} else { | ||
return book; | ||
} | ||
}); | ||
set((state) => ({ | ||
books: updatedBooks, | ||
noOfAvailable: state.noOfAvailable + 1, | ||
noOfIssued: state.noOfIssued - 1, | ||
})); | ||
}, | ||
reset: () => { | ||
set({ | ||
books: [], | ||
noOfAvailable: 0, | ||
noOfIssued: 0, | ||
}); | ||
}, | ||
}); | ||
|
||
const useBookStore = create(bookStore); | ||
|
||
export default useBookStore; | ||
``` | ||
Zustand store๋ hook์ ๋๋ค. ๊ทธ๋์ `useBookStore`๊ฐ ์ปดํฌ๋ํธ ์ด๋ฆ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค. `create`๋ store๋ฅผ ์์ฑํ๋ ๋ฐ ์ฌ์ฉ๋๋ ๋ฉ์๋์ ๋๋ค. store๋ ๊ฐ ์ปดํฌ๋ํธ๊ฐ ๊ณต์ ํ๋ ์ ์ผํ ์ง๋ฆฌ์ ์์ฒ์ ๋๋ค. `set` ํจ์๋ ๋ณ์๋ ๊ฐ์ฒด์ ์ํ๋ฅผ ์์ ํ๋ ๋ฐ ์ฌ์ฉ๋๊ณ , `get` ํจ์๋ ์ก์ ๋ด์์ ์ํ๋ฅผ ์ ๊ทผํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. | ||
์ด ์์ ์์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ store์ ์ํ ๊ฐ์ฒด๋ ์ธ ๊ฐ์ ํ๋๋ฅผ ํฌํจํ๊ณ ์์ต๋๋ค. `books`๋ ์ฑ ์ ID, ์ด๋ฆ, ์ ์์ ๊ฐ์ ์ธ๋ถ ์ ๋ณด๊ฐ ํฌํจ๋ ๋ฐฐ์ด์ ํฌํจํ๋ฉฐ, `noOfAvailable`์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ด์ ์ด ๋์ ์๊ฐ ์ ์ฅ๋ฉ๋๋ค. `noOfIssued`๋ ์ฌ์ฉ์์๊ฒ ๋ฐํ๋ ๋์์ ์ด ์๋ฅผ ์ ์ฅํฉ๋๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ store๋ ๋ค ๊ฐ์ง ๋ฉ์๋๋ฅผ ์ ๊ณตํฉ๋๋ค. `addBook` ํจ์๋ ์ฑ ์ ๋ฐฐ์ด์ ์๋ก์ด ์ฑ ์ ์ถ๊ฐํ๊ณ , ํ์ฌ ์ด์ฉ ๊ฐ๋ฅํ ์ฑ ์ ์๋ฅผ ๋๋ฆฌ๋ฉฐ, ์๋ก ์ถ๊ฐ๋ ๋ชจ๋ ์ฑ ์ ์ํ๋ฅผ "available"๋ก ์ค์ ํฉ๋๋ค. `issueBook` ํจ์๋ ์ฌ์ฉ์๊ฐ ๋์๋ฅผ ๋ฐํํ ์ ์๊ฒ ํด์ฃผ๋ฉฐ, ๊ด๋ จ๋ ๋์๋ "issued" ์ํ๋ก ๋ณ๊ฒฝ๋ฉ๋๋ค. ๋ฐํ ์๊ฐ ์ฆ๊ฐํ๊ณ ์ด์ฉ ๊ฐ๋ฅํ ์ฑ ์ ์๊ฐ ๊ฐ์ํฉ๋๋ค. `returnBook` ํจ์๋ ๋ฐํ๋ ๋์๋ฅผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ฐํํ๋ ๋ฐ ์ฌ์ฉ๋๋ฉฐ, ๋ฐํ๋ ๋์์ ์ํ๊ฐ "available"๋ก ๋ณ๊ฒฝ๋๊ณ ๋ฐํ๋ ๋์ ์๊ฐ ๊ฐ์ํ๋ฉฐ ์ด์ฉ ๊ฐ๋ฅํ ๋์ ์๊ฐ ์ฆ๊ฐํฉ๋๋ค. ๋ง์ง๋ง์ผ๋ก, `reset` ๋ฉ์๋๋ ๋ชจ๋ ์ํ ํ๋๋ฅผ ์ด๊ธฐํํฉ๋๋ค. | ||
#### 4. ์ปดํฌ๋ํธ๋ฅผ store์ ์ฐ๊ฒฐ | ||
๋จผ์ `App.js` ํ์ผ์ ์์ฑํด ๋ด ์๋ค. ์๋ ์ฝ๋๋ฅผ ์ฐธ๊ณ ํ์ธ์. | ||
```javascript | ||
//App.js | ||
|
||
import { useEffect } from 'react'; | ||
import BookForm from './components/BookForm'; | ||
import BookList from './components/BookList'; | ||
import useBookStore from './bookStore'; | ||
import './App.css'; | ||
|
||
function App() { | ||
const reset = useBookStore((state) => state.reset); | ||
|
||
useEffect(() => { | ||
reset(); | ||
}, [reset]); | ||
|
||
return ( | ||
<div className="App"> | ||
<h2>My Library Store</h2> | ||
<BookForm /> | ||
<BookList /> | ||
</div> | ||
); | ||
} | ||
|
||
export default App; | ||
``` | ||
`App.js` ํ์ผ์๋ `BookForm`๊ณผ `BookList`๋ผ๋ ๋ ๊ฐ์ ์ปดํฌ๋ํธ๊ฐ ์์ต๋๋ค. `App` ์ปดํฌ๋ํธ๊ฐ mount๋ ๋๋ง๋ค ์ํ ๋ฐ์ดํฐ๋ฅผ ์ ๊ฑฐํ๊ธฐ ์ํด `reset` ํจ์๋ฅผ ์ฌ์ฉํฉ๋๋ค. | ||
์ด์ `BookForm.js` ์ปดํฌ๋ํธ๋ฅผ ์์ฑํฉ๋๋ค. ์๋ ์ฝ๋๋ฅผ ์ฐธ๊ณ ํ์ธ์. | ||
```javascript | ||
//BookForm.js | ||
|
||
import { useState } from 'react'; | ||
import useBookStore from '../bookStore'; | ||
|
||
function BookForm() { | ||
const addBook = useBookStore((state) => state.addBook); | ||
const [bookDetails, setBookDetails] = useState({}); | ||
|
||
const handleOnChange = (event) => { | ||
const { name, value } = event.target; | ||
setBookDetails({ ...bookDetails, [name]: value }); | ||
}; | ||
|
||
const handleAddBook = () => { | ||
if (!Object.keys(bookDetails).length) return alert('Please enter book details!'); | ||
addBook(bookDetails); | ||
}; | ||
|
||
return ( | ||
<div className="input-div"> | ||
<div className="input-grp"> | ||
<label>Book ID</label> | ||
<input type="text" name="id" size={50} onChange={handleOnChange} /> | ||
</div> | ||
<div className="input-grp"> | ||
<label>Book Name</label> | ||
<input type="text" name="name" size={50} onChange={handleOnChange} /> | ||
</div> | ||
<div className="input-grp"> | ||
<label>Author</label> | ||
<input type="text" name="author" size={50} onChange={handleOnChange} /> | ||
</div> | ||
<button onClick={handleAddBook} className="add-btn"> | ||
Add | ||
</button> | ||
</div> | ||
); | ||
} | ||
|
||
export default BookForm; | ||
``` | ||
`BookForm` ์ปดํฌ๋ํธ๋ ID, ์ด๋ฆ, ์ ์์ ๊ฐ์ ์ฑ ์ ์ธ๋ถ ์ ๋ณด๋ฅผ ์ ๋ ฅํ ์ ์๋ ํผ ํ๋๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค. ๋ํ, ์ด ์ฑ ์ ์ธ๋ถ ์ ๋ณด๋ฅผ ์ ๋ ฅํ๊ธฐ ์ํด ๋์ store์ `addBook` ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ "Add" ๋ฒํผ๋ ์์ต๋๋ค. ์ํ ์ฑ ์ธ๋ถ ์ ๋ณด์ ํจ๊ป UI๊ฐ ์๋์ ํ์๋ฉ๋๋ค. | ||
<img width="500px" src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*q0b70FPKH7oc3BLGk5CMMw.png"/> | ||
๋ค์ ์ฝ๋๋ก BookList.js ์ปดํฌ๋ํธ๋ฅผ ์์ฑํ์ธ์. | ||
```javascript | ||
//BookList.js | ||
|
||
import { Fragment } from 'react'; | ||
import useBookStore from '../bookStore'; | ||
|
||
function BookList() { | ||
const { books, noOfAvailable, noOfIssued, issueBook, returnBook } = useBookStore((state) => ({ | ||
books: state.books, | ||
noOfAvailable: state.noOfAvailable, | ||
noOfIssued: state.noOfIssued, | ||
issueBook: state.issueBook, | ||
returnBook: state.returnBook, | ||
})); | ||
|
||
return ( | ||
<ul className="book-list"> | ||
{!!books?.length && ( | ||
<span className="books-count"> | ||
<h4>Available: {noOfAvailable}</h4> | ||
<h4>Issued: {noOfIssued}</h4> | ||
</span> | ||
)} | ||
{books?.map((book) => { | ||
return ( | ||
<Fragment key={book.id}> | ||
<li className="list-item"> | ||
<span className="list-item-book"> | ||
<span>{book.id}</span> | ||
<span>{book.name}</span> | ||
<span>{book.author}</span> | ||
</span> | ||
<div className="btn-grp"> | ||
<button | ||
onClick={() => issueBook(book.id)} | ||
className={`issue-btn ${book.status === 'issued' ? 'disabled' : ''}`} | ||
disabled={book.status === 'issued'} | ||
> | ||
{' '} | ||
Issue{' '} | ||
</button> | ||
<button | ||
onClick={() => returnBook(book.id)} | ||
className={`return-btn ${book.status === 'available' ? 'disabled' : ''}`} | ||
disabled={book.status === 'available'} | ||
> | ||
{' '} | ||
Return{' '} | ||
</button> | ||
</div> | ||
</li> | ||
</Fragment> | ||
); | ||
})} | ||
</ul> | ||
); | ||
} | ||
|
||
export default BookList; | ||
``` | ||
**BookList** ์ปดํฌ๋ํธ๋ ๋์๊ด์ ์๋ก ์ถ๊ฐ๋ ๋ชจ๋ ์ฑ ์ ํ์ํฉ๋๋ค. ๋ํ ์ด์ฉ ๊ฐ๋ฅ ๋์์ ๋์ถ๋ ๋์์ ์๋ฅผ ๋ณด์ฌ์ค๋๋ค. ๋ชฉ๋ก์ ์๋ ๊ฐ ์ฑ ๊ธฐ๋ก์๋ **Issue**(๋์ถ) ๋ฐ **Return**(๋ฐ๋ฉ) ๋ฒํผ์ด ๋ ๊ฐ ์์ต๋๋ค. UI๋ ๋ค์๊ณผ ๊ฐ์ด ๋ณด์ ๋๋ค. | ||
<img width="500px" src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*mzzOO6phLadV07DhRW4j-w.png"/> | ||
์ UI์๋ ๋ ๊ถ์ ์ฑ ์ด ์์ผ๋ฉฐ, ๊ฐ ์ฑ ๊ธฐ๋ก์ ๋ํด **Issue** ๋ฒํผ์ด ํ์ฑํ๋์ด ์์ต๋๋ค. **Return** ๋ฒํผ์ ๋นํ์ฑํ๋์ด ์์ผ๋ฉฐ, ์ฑ ์ด ๋์ถ๋์์ ๋๋ง ํ์ฑํ๋ฉ๋๋ค. | ||
**Issue** ๋ฒํผ์ ํด๋ฆญํ๋ฉด store์ **issueBook** ๋ฉ์๋๊ฐ ํธ์ถ๋ฉ๋๋ค. ํด๋น ์ฑ ID๊ฐ ์ ๋ฌ๋๊ณ , ํด๋น ์ฑ ์ ์ํ๊ฐ '๋์ถ๋จ(issued)'์ผ๋ก ์ค์ ๋ฉ๋๋ค. ๊ทธ๋ฐ ๋ค์, ๊ด๋ จ๋ **Issue** ๋ฒํผ์ด ๋นํ์ฑํ๋๊ณ , **Return** ๋ฒํผ์ด ํ์ฑํ๋ฉ๋๋ค. ์ฌ์ฉ ๊ฐ๋ฅํ ์ฑ ์ ์๊ฐ ์ค์ด๋ค๊ณ , ๋์ถ๋ ์ฑ ์ ์๊ฐ ์ฆ๊ฐํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ์๋ ์คํฌ๋ฆฐ์ท์ ์ฐธ์กฐํ์ธ์. | ||
<img width="500px" src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*XmKsjPTJCcBU-cMeHrj8OQ.png"/> | ||
**Return** ๋ฒํผ์ ํด๋ฆญํ๋ฉด store์ **returnBook** ๋ฉ์๋๊ฐ ํธ์ถ๋ฉ๋๋ค. ํด๋น ์ฑ ID๊ฐ ์ ๋ฌ๋๊ณ , ํด๋น ์ฑ ์ ์ํ๊ฐ '์ด์ฉ ๊ฐ๋ฅ(available)'์ผ๋ก ๋ค์ ์ค์ ๋ฉ๋๋ค. ๊ด๋ จ๋ **Return** ๋ฒํผ์ด ๋นํ์ฑํ๋๊ณ , **Issue** ๋ฒํผ์ด ๋ค์ ํ์ฑํ๋ฉ๋๋ค. ์ฌ์ฉ ๊ฐ๋ฅํ ์ฑ ์ ์๊ฐ ์ฆ๊ฐํ๊ณ , ๋์ถ๋ ์ฑ ์ ์๊ฐ ๊ฐ์ํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ์ ์คํฌ๋ฆฐ์ท์ ์ฐธ์กฐํ์ธ์. | ||
> Zustand store๋ ํ (hook)์ด๊ธฐ ๋๋ฌธ์ ์ด๋์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. Redux๋ Redux Toolkit๊ณผ๋ ๋ฌ๋ฆฌ context provider๊ฐ ํ์ํ์ง ์์ต๋๋ค. ์ํ๋ฅผ ์ ํํ๊ธฐ๋ง ํ๋ฉด, ์ํ๊ฐ ๋ณ๊ฒฝ๋ ๋ ์ปดํฌ๋ํธ๊ฐ ๋ค์ ๋ ๋๋ง๋ฉ๋๋ค. ํ์ํ ์ฌ๋ผ์ด์ค๋ง ์ป๊ธฐ ์ํด **useBookStore**์ ์ ํ๊ธฐ๋ฅผ ์ ๊ณตํด์ผ ํฉ๋๋ค. | ||
์์ง ๋๋์ง ์์์ต๋๋ค! Zustand์๋ ํฐ ์ฅ์ ์ด ์์ต๋๋ค: **๋ฏธ๋ค์จ์ด(Middlewares)**์ ๋๋ค. | ||
### Zustand ๋ฏธ๋ค์จ์ด | ||
Zustand๋ ๋ฏธ๋ค์จ์ด์ ํจ๊ป ์ฌ์ฉํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ ๋ง์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ์ ์์ต๋๋ค. ๊ฐ์ฅ ๋๋ฆฌ ์ฌ์ฉ๋๋ Zustand ๋ฏธ๋ค์จ์ด๋ ์๋์ ๋์ ์์ต๋๋ค. | ||
1. **Redux DevTools** | ||
Redux DevTools๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ํ ๋ณ๊ฒฝ์ ๋๋ฒ๊ทธํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. Zustand์์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. Chrome ํ์ฅ ํ๋ก๊ทธ๋จ์ผ๋ก Redux DevTools๋ฅผ ์ค์นํ๋์ง ํ์ธํ์ธ์. ์๋ ์ฝ๋๋ฅผ ์ฌ์ฉํ์ฌ ํตํฉํ ์ ์์ต๋๋ค. | ||
```javascript | ||
import { create } from 'zustand'; | ||
import { devtools } from 'zustand/middleware'; | ||
|
||
const bookStore = (set, get) => ({ | ||
books: [], | ||
noOfAvailable: 0, | ||
noOfIssued: 0, | ||
addBook: (book) => { | ||
set((state) => ({ | ||
books: [...state.books, { ...book, status: 'available' }], | ||
noOfAvailable: state.noOfAvailable + 1, | ||
})); | ||
}, | ||
}); | ||
|
||
const useBookStore = create(devtools(bookStore)); | ||
|
||
export default useBookStore; | ||
``` | ||
**devtools**๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด **zustand/middleware**์์ ๊ฐ์ ธ์์ผ ํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ **create** ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ store๋ฅผ **devtools**๋ก ๋ํํฉ๋๋ค. | ||
์น ๋ธ๋ผ์ฐ์ ์์ Redux DevTools๋ฅผ ์ด๊ณ ์ํ๋ฅผ ๊ฒ์ฌํฉ๋๋ค. ์๋ ์คํฌ๋ฆฐ์ท์ ๋์ ์์ต๋๋ค. | ||
<img width="500px" src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*Tjau7gmPB8xsuMRfF08Ukw.png"/> | ||
2. **Persist Middleware** | ||
**Persist** ๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ๋ฉด ๋ชจ๋ ์ข ๋ฅ์ ํด๋ผ์ด์ธํธ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ์ฌ ์ํ๋ฅผ ์ง์์ํฌ ์ ์์ต๋๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์ ์๋ก๊ณ ์นจํด๋ store์ ๋ฐ์ดํฐ๊ฐ ์ ์ฅ์์ ๋จ์ ์์ต๋๋ค. ์ด ๋ฏธ๋ค์จ์ด๋ฅผ ์ถ๊ฐํ๋ ์ฝ๋๋ ์๋๋ฅผ ์ฐธ์กฐํ์ธ์. | ||
```javascript | ||
import { create } from 'zustand'; | ||
import { persist, createJSONStorage } from 'zustand/middleware'; | ||
|
||
const bookStore = (set, get) => ({ | ||
books: [], | ||
noOfAvailable: 0, | ||
noOfIssued: 0, | ||
addBook: (book) => { | ||
set((state) => ({ | ||
books: [...state.books, { ...book, status: 'available' }], | ||
noOfAvailable: state.noOfAvailable + 1, | ||
})); | ||
}, | ||
}); | ||
|
||
const useBookStore = create( | ||
persist(bookStore, { | ||
name: 'books', | ||
storage: createJSONStorage(() => sessionStorage), | ||
}) | ||
); | ||
|
||
export default useBookStore; | ||
``` | ||
**persist**๋ฅผ **zustand/middleware**์์ ๊ฐ์ ธ์ต๋๋ค. ๊ทธ๋ฐ ๋ค์ **create** ๋ฉ์๋ ๋ด์์ store๋ฅผ **persist**๋ก ๋ํํฉ๋๋ค. ์ ์ฅ์์ ํญ๋ชฉ์ ๊ณ ์ ํ ์ด๋ฆ์ ๊ฐ์ง ์ ์์ต๋๋ค. ๋ํ ์ ์ฅ์์ ์ข ๋ฅ๋ฅผ ์ง์ ํ ์ ์์ต๋๋ค. ์ด ์ฝ๋์์๋ **sessionStorage**๊ฐ ์ฐธ์กฐ๋ฉ๋๋ค. ์๋ฌด๊ฒ๋ ์ง์ ํ์ง ์์ผ๋ฉด ๊ธฐ๋ณธ๊ฐ์ผ๋ก **localStorage**๊ฐ ์ฌ์ฉ๋ฉ๋๋ค. | ||
์น ๋ธ๋ผ์ฐ์ ๋ฅผ ์ด๊ณ ์ธ์ ์คํ ๋ฆฌ์ง์์ ์ ์ฅ๋ ์ํ๋ฅผ ํ์ธํ์ธ์. ์คํฌ๋ฆฐ์ท์ ์๋์ ๊ฐ์ต๋๋ค. | ||
<img width="500px" src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*eVw9beZHm-3AgIJRF6XnGA.png"/> | ||
### ์์ฝ | ||
์ด ๊ธ์์ ์ธ๊ธํ๋ฏ์ด, ์ํ ๊ด๋ฆฌ๋ React ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋งค์ฐ ์ค์ํฉ๋๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ปค์ง์๋ก ์ํ๋ฅผ ๊ด๋ฆฌํ ๊ฐ๋ ฅํ ๋ฐฉ๋ฒ์ ์ ํํด์ผ ํฉ๋๋ค. Zustand ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ React ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐ ์ด์์ ์ธ ํด๊ฒฐ์ฑ ์ ๋๋ค. Redux๋ณด๋ค ํจ์ฌ ๊ฐ๋จํ๊ณ ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋๊ฐ ์ ์ต๋๋ค. ๋ํ, ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ํฉํ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐพ๊ธฐ ์ํด ๋ค์ํ ์ต์ ์ ํ์ํ๋ ๊ฒ๋ ์ข์ต๋๋ค. | ||
### ์ฐธ๊ณ | ||
1. https://docs.pmnd.rs/zustand/getting-started/introduction | ||
2. https://www.youtube.com/watch?v=KCr-UNsM3vA | ||
3. https://www.youtube.com/watch?v=fZPgBnL2x-Q |