From 4f056bc69e1249088da6fed19fd9974edcc3a31a Mon Sep 17 00:00:00 2001 From: Soyeon Choe Date: Mon, 19 Aug 2024 09:06:13 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20React=20State=20Management=20=E2=80=94?= =?UTF-8?q?=20using=20Zustand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../React-State-Management-using-Zustand.md | 385 ++++++++++++++++++ 1 file changed, 385 insertions(+) create mode 100644 Aug/article/React-State-Management-using-Zustand.md diff --git a/Aug/article/React-State-Management-using-Zustand.md b/Aug/article/React-State-Management-using-Zustand.md new file mode 100644 index 0000000..d5b31a9 --- /dev/null +++ b/Aug/article/React-State-Management-using-Zustand.md @@ -0,0 +1,385 @@ +## πŸ”— [React State Management β€” using Zustand](https://medium.com/globant/react-state-management-b0c81e0cbbf3) + +### πŸ—“οΈ λ²ˆμ—­ λ‚ μ§œ: 2024.08.19 + +### 🧚 λ²ˆμ—­ν•œ 크루: μ†Œν•˜(μ΅œμ†Œμ—°) + +--- + + + +당신은 μƒνƒœ(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κ°€ μ–΄λ–»κ²Œ μž‘λ™ν•˜λŠ”μ§€ ν™•μΈν•΄λ³΄μ„Έμš”. + + + +λ¨Όμ €, μ•žμ„œ μ„€λͺ…λœ μ•„ν‚€ν…μ²˜μ—μ„œ λ³Ό 수 μžˆλ“―μ΄ ν”„λ‘ νŠΈμ—”λ“œ μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€κ°€ μžˆμŠ΅λ‹ˆλ‹€. action creatorsλŠ” 각 μ‚¬μš©μž μš”μ²­μ— λŒ€ν•΄ μ˜¬λ°”λ₯Έ μ•‘μ…˜μ΄ νŠΈλ¦¬κ±°λ˜λ„λ‘ 보μž₯ν•©λ‹ˆλ‹€. μ•‘μ…˜μ€ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ μ–΄λ–€ 일이 λ°œμƒν–ˆλŠ”μ§€λ₯Ό μ„€λͺ…ν•˜λŠ” 이벀트라고 생각할 수 μžˆμŠ΅λ‹ˆλ‹€. λ²„νŠΌ ν΄λ¦­μ΄λ‚˜ 검색 μˆ˜ν–‰ 같은 것이 이에 ν•΄λ‹Ήν•  수 μžˆμŠ΅λ‹ˆλ‹€. dispatchersλŠ” μ΄λŸ¬ν•œ μ•‘μ…˜μ„ store둜 λ³΄λ‚΄λŠ” 데 도움을 μ€λ‹ˆλ‹€. 이후 reducersλŠ” μƒνƒœλ₯Ό μ–΄λ–»κ²Œ μ²˜λ¦¬ν• μ§€ κ²°μ •ν•©λ‹ˆλ‹€. reducer ν•¨μˆ˜λŠ” ν˜„μž¬ μƒνƒœμ™€ μ•‘μ…˜ 객체λ₯Ό λ°›μ•„ μƒνƒœλ₯Ό λ³€κ²½ν•©λ‹ˆλ‹€. ν•„μš”μ— 따라 μƒˆλ‘œμš΄ μƒνƒœλ₯Ό λ°˜ν™˜ν•˜λ©°, μ—…λ°μ΄νŠΈλœ μƒνƒœ μˆ˜μ • 사항이 UIλ₯Ό λ Œλ”λ§ν•˜κ²Œ λ©λ‹ˆλ‹€. + +이제 ν₯미둜운 뢀뢄이 λ‹€κ°€μ˜΅λ‹ˆλ‹€! μ•„λž˜μ— 제곡된 μ•„ν‚€ν…μ²˜λ₯Ό 톡해 Zustandκ°€ μ–΄λ–»κ²Œ μž‘λ™ν•˜λŠ”μ§€ μ‚΄νŽ΄λ³΄μ„Έμš”. 이 λ‹¨μˆœν™”λœ λ‹€μ΄μ–΄κ·Έλž¨μ— ν₯λ―Έλ₯Ό λŠλ‚„ μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. + + + +μ—¬κΈ°μ—μ„œλ„ 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 ( +
+

My Library Store

+ + +
+ ); +} + +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 ( +
+
+ + +
+
+ + +
+
+ + +
+ +
+ ); +} + +export default BookForm; +``` + +`BookForm` μ»΄ν¬λ„ŒνŠΈλŠ” ID, 이름, μ €μžμ™€ 같은 μ±…μ˜ μ„ΈλΆ€ 정보λ₯Ό μž…λ ₯ν•  수 μžˆλŠ” 폼 ν•„λ“œλ₯Ό 가지고 μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ, 이 μ±…μ˜ μ„ΈλΆ€ 정보λ₯Ό μž…λ ₯ν•˜κΈ° μœ„ν•΄ λ„μ„œ store의 `addBook` λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜λŠ” "Add" λ²„νŠΌλ„ μžˆμŠ΅λ‹ˆλ‹€. μƒ˜ν”Œ μ±… μ„ΈλΆ€ 정보와 ν•¨κ»˜ UIκ°€ μ•„λž˜μ— ν‘œμ‹œλ©λ‹ˆλ‹€. + + + +λ‹€μŒ μ½”λ“œλ‘œ 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 ( + + ); +} + +export default BookList; +``` + +**BookList** μ»΄ν¬λ„ŒνŠΈλŠ” λ„μ„œκ΄€μ— μƒˆλ‘œ μΆ”κ°€λœ λͺ¨λ“  책을 ν‘œμ‹œν•©λ‹ˆλ‹€. λ˜ν•œ 이용 κ°€λŠ₯ λ„μ„œμ™€ λŒ€μΆœλœ λ„μ„œμ˜ 수λ₯Ό λ³΄μ—¬μ€λ‹ˆλ‹€. λͺ©λ‘μ— μžˆλŠ” 각 μ±… κΈ°λ‘μ—λŠ” **Issue**(λŒ€μΆœ) 및 **Return**(λ°˜λ‚©) λ²„νŠΌμ΄ 두 개 μžˆμŠ΅λ‹ˆλ‹€. UIλŠ” λ‹€μŒκ³Ό 같이 λ³΄μž…λ‹ˆλ‹€. + + + +μœ„ UIμ—λŠ” 두 ꢌ의 책이 있으며, 각 μ±… 기둝에 λŒ€ν•΄ **Issue** λ²„νŠΌμ΄ ν™œμ„±ν™”λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. **Return** λ²„νŠΌμ€ λΉ„ν™œμ„±ν™”λ˜μ–΄ 있으며, 책이 λŒ€μΆœλ˜μ—ˆμ„ λ•Œλ§Œ ν™œμ„±ν™”λ©λ‹ˆλ‹€. + +**Issue** λ²„νŠΌμ„ ν΄λ¦­ν•˜λ©΄ store의 **issueBook** λ©”μ„œλ“œκ°€ ν˜ΈμΆœλ©λ‹ˆλ‹€. ν•΄λ‹Ή μ±… IDκ°€ μ „λ‹¬λ˜κ³ , ν•΄λ‹Ή μ±…μ˜ μƒνƒœκ°€ 'λŒ€μΆœλ¨(issued)'으둜 μ„€μ •λ©λ‹ˆλ‹€. 그런 λ‹€μŒ, κ΄€λ ¨λœ **Issue** λ²„νŠΌμ΄ λΉ„ν™œμ„±ν™”λ˜κ³ , **Return** λ²„νŠΌμ΄ ν™œμ„±ν™”λ©λ‹ˆλ‹€. μ‚¬μš© κ°€λŠ₯ν•œ μ±…μ˜ μˆ˜κ°€ 쀄어듀고, λŒ€μΆœλœ μ±…μ˜ μˆ˜κ°€ μ¦κ°€ν•˜λŠ” 것을 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. μ•„λž˜ μŠ€ν¬λ¦°μƒ·μ„ μ°Έμ‘°ν•˜μ„Έμš”. + + + +**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λ₯Ό μ—΄κ³  μƒνƒœλ₯Ό κ²€μ‚¬ν•©λ‹ˆλ‹€. μ•„λž˜ μŠ€ν¬λ¦°μƒ·μ— λ‚˜μ™€ μžˆμŠ΅λ‹ˆλ‹€. + + + +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**κ°€ μ‚¬μš©λ©λ‹ˆλ‹€. + +μ›Ή λΈŒλΌμš°μ €λ₯Ό μ—΄κ³  μ„Έμ…˜ μŠ€ν† λ¦¬μ§€μ—μ„œ μ €μž₯된 μƒνƒœλ₯Ό ν™•μΈν•˜μ„Έμš”. μŠ€ν¬λ¦°μƒ·μ€ μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€. + + + +### μš”μ•½ + +이 κΈ€μ—μ„œ μ–ΈκΈ‰ν–ˆλ“―μ΄, μƒνƒœ κ΄€λ¦¬λŠ” 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