Trying Out Redux Toolkit

notion image
![NpmTrends](images/redux.png)
--- title: "Trying Out Redux Toolkit" author: "Hun Im" date: 2022-05-19T16:53:04+09:00 category: ["POSTS"] tags: ["React"] og_image: "/images/gamer.png" keywords: ['React'] --- Throughout my projects, I've used Redux for state management. There are many state management libraries available. Recently, Iโ€™ve heard of libraries like MobX, Recoil, Zustand, and so on. ![NpmTrends](images/redux.png) While I haven't applied other state management solutions directly in my projects, the statistics indicate that Redux is overwhelmingly popular. So far, I havenโ€™t experienced significant inconvenience while using React and Redux. However, I found the back-and-forth process of changing type definitions, action functions, and reducers in Redux to be cumbersome. When upgrading to React 18, I discovered that the syntax I had been using was considered "legacy." I vaguely understood that the Redux Toolkit library could alleviate the inconveniences of traditional Redux. At the time, I was busy with development and tried to apply the methods I found on blogs without thoroughly reading the official documentation. Ultimately, I failed to refactor my existing Redux code and decided to revert to my well-structured existing Redux code, which I deemed to be fine. For this new project, I want to explore Redux Toolkit from the beginning, so Iโ€™m summarizing my findings here. **Redux** 1. Redux is a state container 2. The state of the application can change 3. In redux, a pattern is enforced to ensure all state 4. transitions are explicit and can be tracked - The changes to your application's state become predictable - If you want to manage the global state of your application in a predictable way, redux can help you - The patterns and tools provided by Redux make it easier to understand, when, where, why, and how the state in your application is being updated, and how your application logic will behave when those changes occur - Redux guides you towords writing code that is predictable and testable, which helps give you confidence that your application will work as exected **Redux Toolkit** 1. Redux toolkit is the officail, opinionated, batteries-included toolset for efficient Redux development. 2. It is also intended to be the standard way to write Redux logic in your application **Why Use Redux Toolkit?** 1. Redux is great, but it does have a few shortcomings, - Configuring redux in an app seems complicated. - In addition to redux, a lot of other packages have to be installed to get redux to do something useful. - Redux requires too much boilerplate code 2. Redux toolkit serves as an abstraction over redux. It hides the difficult parts ensuring you have a good developer experience. **When Should You Use Redux?** 1. You have large amounts of application state that are needed in many places in the app 2. The app state is updated frequently over time 3. The logic to update that state may be complex 4. The app has a medium or large-sized codebase, and might be worked on by many people **Immer Library** ๋ฆฌ๋•์Šค ๊ฐ์ฒด๋ฅผ ๋‹ค๋ฃฐ๋•Œ, immutable์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด rest parameter ํ˜น์€ ๋ฐฐ์—ด์˜ ๋‚ด์žฅํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด์„œ ๋ถˆ๋ณ€์„ฑ์„ ๊ตฌํ˜„ ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง€๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค. immer๊ฐ€ ์ด๋Ÿฌํ•œ ๋ถˆ๋ณ€์„ฑ์„ ๊ด€๋ฆฌ ํ•ด์ค€๋‹ค. **Three Key Concepts** 1. store : holds the state of your application. 2. action : describes what happened in the application 3. reducer : handles the action and decides how to update the state **Redux Toolkit Syntax** 1. Creating a Store: ```ts import { configureStore } from "@reduxjs/toolkit"; // ... export const store = configureStore({ reducer: { posts: postsReducer, comments: commentsReducer, users: usersReducer, }, }); // Infer the `RootState` and `AppDispatch` types from the store itself export type RootState = ReturnType<typeof store.getState>; // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} export type AppDispatch = typeof store.dispatch; ``` **Advantages** 1. Using Hooks: There was a cumbersome problem of having to declare types every time when using `useDispatch` and `useSelector`. By using hooks, we can eliminate this boilerplate code! ```ts // hooks/index.ts import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; import type { RootState, AppDispatch } from "./store"; // Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch = () => useDispatch<AppDispatch>(); export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; ``` ```ts //? Component that dispatches actions import { useAppDispatch } from "hooks"; const dispatch = useAppDispatch(); dispatch(๋ฆฌ๋“€์„œํ•จ์ˆ˜()); ``` 2. CreateSlice Syntax: ```ts import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import type { RootState } from "../../app/store"; // Define a type for the slice state interface CounterState { value: number; } // Define the initial state using that type const initialState: CounterState = { value: 0, }; export const counterSlice = createSlice({ name: "counter", // `createSlice` will infer the state type from the `initialState` argument initialState, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, // Use the PayloadAction type to declare the contents of `action.payload` incrementByAmount: (state, action: PayloadAction<number>) => { state.value += action.payload; }, }, }); export const { increment, decrement, incrementByAmount } = counterSlice.actions; // Other code such as selectors can use the imported `RootState` type export const selectCount = (state: RootState) => state.counter.value; export default counterSlice.reducer; ``` After this, you just need to dispatch the action functions you want to use.
ย 
--- title: "๋ฆฌ๋•์Šคํˆดํ‚ท ์ฐ๋จนํ•˜๊ธฐ" author: "์ž„ํ›ˆ" date: 2022-05-19T16:53:04+09:00 category: ["POSTS"] tags: ["React"] og_image: "/images/gamer.png" keywords: ['React'] --- ์ง€๊ธˆ๊ป ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋ฉด์„œ, ๋ฆฌ๋•์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ํ•ด์™”๋‹ค. ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์ •๋ง ๋งŽ๋‹ค. ์ตœ๊ทผ์— ๋“ค์–ด๋ณธ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” Mobx , Recoil, Zustand ๋“ฑ๋“ฑ.. ![NpmTrends](images/redux.png) ๋‹ค๋ฅธ ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ์ง์ ‘ ํ”„๋กœ์ ํŠธ์—์„œ ์ ์šฉํ•ด๋ณด์ง„ ์•Š์•˜์ง€๋งŒ, ํ†ต๊ณ„์ ์œผ๋กœ ์••๋„์ ์ด๋‹ค. ์ง€๊ธˆ๊นŒ์ง€ ๋ฆฌ์•กํŠธ์™€ ๋ฆฌ๋•์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ํฐ ๋ถˆํŽธํ•จ์„ ๋А๋‚€์ ์€ ์—†์—ˆ๋‹ค. ํ•˜์ง€๋งŒ, ๋ฆฌ๋•์Šค์˜ ํƒ€์ž…์ •์˜, ์•ก์…˜ํ•จ์ˆ˜, ๋ฆฌ๋“€์„œ๋ฅผ ์™”๋‹ค๊ฐ”๋‹ค ํ•˜๋ฉฐ ๋ณ€๊ฒฝํ•ด์ค˜์•ผํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€์ด ์žˆ์—ˆ๋‹ค. ๋ฆฌ์•กํŠธ 18๋ฒ„์ „์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ ํ•˜๋ฉด์„œ, ๊ธฐ์กด์— ๋‚ด๊ฐ€ ์‚ฌ์šฉํ•˜๋˜ ๋ฆฌ๋•์Šค์˜ ๋ฌธ๋ฒ•์ด Legacyํ•œ ๋ฌธ๋ฒ•์ด๋ผ๊ณ  ํ•˜์—ฌ ์ฐพ๋‹ค๋ณด๋‹ˆ ๋ฆฌ๋•์Šค ํˆดํ‚ท์ด๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๊ธฐ์กด์˜ ๋ฆฌ๋•์Šค์˜ ๋ถˆํŽธํ•จ์„ ๋ณด์™„ ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์–ด๋ ดํ’‹ํ•˜๊ฒŒ ์•Œ๊ณ ์žˆ์—ˆ๋‹ค. ๋‹น์‹œ ๊ฐœ๋ฐœํ•˜๊ธฐ ๋ฐ”์˜๋‹ˆ๊น, ๊ผผ๊ผผํ•˜๊ฒŒ ๊ณต์‹๋ฌธ์„œ๋ฅผ ์ฝ์–ด๋ณด์ง€ ์•Š๊ณ , ๋Œ€์ถฉ ๋ธ”๋กœ๊ทธ์—์„œ ์ฐพ์€ ๋ฐฉ๋ฒ• ๊ทธ๋Œ€๋กœ ์ ์šฉํ•˜๋ ค๊ณ  ์• ์ผ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ ๊ธฐ์กด์˜ ๋ฆฌ๋•์Šค ์ฝ”๋“œ๋ฅผ ๋ฆฌํŒฉํ„ฐ๋งํ•˜๊ธฐ์— ์‹คํŒจํ•˜๊ณ , ๊ธฐ์กด ๋ฆฌ๋•์Šค ์ฝ”๋“œ ๋˜ํ•œ ๊ตฌ์กฐ๋„ ์ž˜ ๊ฐ–์ถ”์—ˆ๊ณ , ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค๊ณ  ํŒ๋‹จํ•˜์—ฌ, ๋‹ค์‹œ ๊ธฐ์กด ๋ฆฌ๋•์Šค ์ฝ”๋“œ๋กœ ๋Œ์•„์™”๋‹ค. ์ด๋ฒˆ ์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ์—๋Š” ๋ฆฌ๋•์Šค ํˆดํ‚ท์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ์‚ฌ์šฉํ•ด๋ณด๊ณ ์ž, ์ •๋ฆฌํ•˜์—ฌ ๊ธ€๋กœ์จ ๋‚จ๊ฒจ๋ณธ๋‹ค. **๋ฆฌ๋•์Šค** 1. Redux is a state container 2. The state of the application can change 3. In redux, a pattern is enforced to ensure all state 4. transitions are explicit and can be tracked - The changes to your application's state become predictable - If you want to manage the global state of your application in a predictable way, redux can help you - The patterns and tools provided by Redux make it easier to understand, when, where, why, and how the state in your application is being updated, and how your application logic will behave when those changes occur - Redux guides you towords writing code that is predictable and testable, which helps give you confidence that your application will work as exected **๋ฆฌ๋•์Šค ํˆดํ‚ท** 1. Redux toolkit is the officail, opinionated, batteries-included toolset for efficient Redux development. 2. It is also intended to be the standard way to write Redux logic in your application **๋ฆฌ๋•์Šค ํˆดํ‚ท์„ ์“ฐ๋Š” ์ด์œ ** 1. Redux is great, but it does have a few shortcomings, - Configuring redux in an app seems complicated. - In addition to redux, a lot of other packages have to be installed to get redux to do something useful. - Redux requires too much boilerplate code 2. Redux toolkit serves as an abstraction over redux. It hides the difficult parts ensuring you have a good developer experience. **์–ธ์ œ ๋ฆฌ๋•์Šค๋ฅผ ์จ์•ผํ•˜๋Š”๊ฐ€?** 1. You have large amounts of application state that are needed in many places in the app 2. The app state is updated frequently over time 3. The logic to update that state may be complex 4. The app has a medium or large-sized codebase, and might be worked on by many people **Immer ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ** ๋ฆฌ๋•์Šค ๊ฐ์ฒด๋ฅผ ๋‹ค๋ฃฐ๋•Œ, immutable์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด rest parameter ํ˜น์€ ๋ฐฐ์—ด์˜ ๋‚ด์žฅํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด์„œ ๋ถˆ๋ณ€์„ฑ์„ ๊ตฌํ˜„ ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง€๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค. immer๊ฐ€ ์ด๋Ÿฌํ•œ ๋ถˆ๋ณ€์„ฑ์„ ๊ด€๋ฆฌ ํ•ด์ค€๋‹ค. **์„ธ๊ฐ€์ง€ ์ปจ์…‰** 1. store : holds the state of your application. 2. action : describes what happened in the application 3. reducer : handles the action and decides how to update the state **๋ฆฌ๋•์Šคํˆดํ‚ท ๋ฌธ๋ฒ•** 1. ์Šคํ† ์–ด ์ƒ์„ฑ ```ts import { configureStore } from "@reduxjs/toolkit"; // ... export const store = configureStore({ reducer: { posts: postsReducer, comments: commentsReducer, users: usersReducer, }, }); // Infer the `RootState` and `AppDispatch` types from the store itself export type RootState = ReturnType<typeof store.getState>; // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} export type AppDispatch = typeof store.dispatch; ``` **์žฅ์ ** 1. Hooks์‚ฌ์šฉ ๋งค๋ฒˆ useDispatch์™€ useSelector๋ฅผ ์„ ์–ธํ• ๋•Œ ํƒ€์ž…๊นŒ์ง€ ์ƒํƒœ ํƒ€์ž…๊นŒ์ง€ ์„ค์ •ํ•ด์•ผํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์šด ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค. hooks๋ฅผ ์ด์šฉํ•˜์—ฌ, ์ด๋Ÿฌํ•œ ๋ฒˆ๊ฑฐ๋กœ์šด ์ฝ”๋“œ๋ฅผ ์—†์• ์ž!! ```ts // hooks/index.ts import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; import type { RootState, AppDispatch } from "./store"; // Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch = () => useDispatch<AppDispatch>(); export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; ``` ```ts //? dispatch๋ฅผ ์‹คํ–‰ํ•  ์ปดํฌ๋„ŒํŠธ import { useAppDispatch } from "hooks"; const dispatch = useAppDispatch(); dispatch(๋ฆฌ๋“€์„œํ•จ์ˆ˜()); ``` 2. CreateSlice ๋ฌธ๋ฒ• ```ts import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import type { RootState } from "../../app/store"; // Define a type for the slice state interface CounterState { value: number; } // Define the initial state using that type const initialState: CounterState = { value: 0, }; export const counterSlice = createSlice({ name: "counter", // `createSlice` will infer the state type from the `initialState` argument initialState, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, // Use the PayloadAction type to declare the contents of `action.payload` incrementByAmount: (state, action: PayloadAction<number>) => { state.value += action.payload; }, }, }); export const { increment, decrement, incrementByAmount } = counterSlice.actions; // Other code such as selectors can use the imported `RootState` type export const selectCount = (state: RootState) => state.counter.value; export default counterSlice.reducer; ``` ์ดํ›„, ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ actionํ•จ์ˆ˜๋ฅผ dispatchํ•˜๋ฉด ๋œ๋‹ค.
ย