ํ์ ์คํฌ๋ฆฝํธ , ๋ฆฌ์กํธ , ๋ฆฌ๋์ค๋ฅผ ๊ฐ์ด ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์.
์ผ์ผํ ํ์ ์คํฌ๋ฆฝํธ , ๋ฆฌ์กํธ , ๋ฆฌ๋์ค ์ ํ ์ ํ ํ์์์ด Create a React Redux App ํ ํ๋ฆฟ์ ์ฌ์ฉํ๋ฉด ์ ํ ์ด ์๋ฃ๋ ํ ํ๋ฆฟ ์ํ๋ก ํ๋ก์ ํธ๋ฅผ ์์ํ ์ ์๋ค.
๋ช
๋ น์ด # Redux + TypeScript template
npx create-react-app my-app --template redux-typescript
์ ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํ ๋ค ์กฐ๊ธ ๊ธฐ๋ค๋ฆฌ๋ฉด ๋ค์๊ณผ ๊ฐ์ ํด๋ ๊ตฌ์กฐ๋ฅผ ๋ณผ ์ ์๋ค.
์ถํ์ ๊ฐ๋ฐํ ๋ ๋๋ ํฐ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋ฐ๊ฟ๋ ์๊ด์๋ค.
๊ทธ ์ธ์ ๋๋จธ์ง ๊ตฌ์กฐ๋ ๊ธฐ์กด CRA ์ ํ ๊ณผ ๋น์ทํ๋ค.
index.tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './app/store';
import App from './App';
import reportWebVitals from './reportWebVitals';
import './index.css';
const container = document.getElementById('root')!;
const root = createRoot(container);
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
๊ธฐ๋ณธ ์ ํ ์ผ๋ก ํ๋ก์ ํธ์์ store๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก Provider๋ก ๊ฐ์ธ์ฃผ๊ณ store๊ฐ์ props๋ก ํ ๋นํ๊ณ ์๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
์ด ๋์ index.tsx์์ Provider(from react-redux), store ์ ํ ์ ํ๋ ๋ฒ๊ฑฐ๋ก์์ ์ค์ผ ์ ์๋ค.
์ง๊ณ ๋์ด๊ฐ๊ธฐ
ํ๋ก์ ํธ์ redux ์ํ๊ฐ ์ฌ์ฉ ๋ฐ dispatch ๋ฑ์ store์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ์ต์๋จ ๋ถ๋ชจ์ปดํฌ๋ํธ(App.tsx)๋ฅผ Provider๋ก ๊ฐ์ธ์ฃผ๊ณ store๋ฅผ props๊ฐ์ผ๋ก ํ ๋นํด์ค์ผํ๋ค.
src/app/store.ts
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
store.ts๋ฅผ ์ดํด๋ณด์.
store.ts๋ store๋ฅผ ์์ฑํ๊ณ Dispatchํ์ , Stateํ์ ์ ํธํ๊ฒ ์ฌ์ฉํ๋๋ก ์ง์ ํด์ฃผ๊ณ ์๋ค.
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
configureStore API๋ฅผ ํตํ์ฌ store๋ฅผ ๊ตฌ์ฑํ๊ณ reducer์ ์ฌ๋ฌ slice reducer๋ฅผ ํ ๋นํด์ค ์ ์๋ค.
AppDispatch ํ์ ์ store.dispatch์ ํ์ ์ ์ฌ์ฉํ๊ธฐ ํธ๋ฆฌํ๋๋ก ๋ฏธ๋ฆฌ ์ ์ํด์ค๋ค.
export type AppDispatch = typeof store.dispatch
store.dispatch
store์ ๋ฉ์๋๋ก์ ์ก์ ๊ฐ์ ๋ณด๋ด๋ฏ๋ก์จ ์ํ ๋ณ๊ฒฝ
dispatch๊ฐ ๋ฐํํ๋ ๊ฐ์ ๋์คํจ์น๋ ์ก์ ์ด๋ค.
์ปค์คํ ํ์ฌ ์ฌ์ฉํ๋ ์ด์ ๋ useDispatch ํ์ ์ ์ฌ์ฉํ๊ธฐ ์ํด์์ด๋ค.
hook.ts์์ ํ์ธํด๋ณด๋ฉด useDispach๋ฅผ ์ปค์คํ ํ์ฌ ์ฌ์ฉํ๊ณ ์๋ค.
hook.ts
import type { AppDispatch } from "./store";
export const useAppDispatch = () => useDispatch<AppDispatch>();
useDispatch์ dispatch ํ์ ์ ์ง์ ํ์ฌ ์ฌ์ฉํด์ฃผ๊ธฐ ์ํด AppDispatch ํ์ ์ ์ปค์คํ ํ์ฌ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค.
RootState ์ปค์คํ ํ์ ์ store.getState์ ๋ฐํ๊ฐ ํ์ ์ type์ผ๋ก ์ฌ์ฉํ๋ฉฐ state๋ฅผ selectํ ๋ state ํ์ ์ ์ ์ํ๋๋ฐ ์ฌ์ฉ๋๋ค.
export type RootState = ReturnType<typeof store.getState>
export const selectCount = (state: RootState) => state.counter.value;
store.getState
store์ ๋ฉ์๋๋ก์จ store์ ํ์ฌ state tree๋ฅผ ๋ฐํํด์ค๋ค.
RootState๋ฅผ ์ปค์คํ ํด๋์ง ์์ผ๋ฉด ์๋์ ๊ฐ์ด ์ฝ๋๊ฐ ๊ธธ์ด์ง ์ ์๋ค.
export const selectCount = (state: ReturnType<typeof store.getState>) => state.counter.value;
import { ThunkAction, Action } from "@reduxjs/toolkit";
...
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
thunkํจ์์ ํ์
์ ์ ์ํ ๋ ์ฌ์ฉํ ํ์
์ด๋ค.
์์์ Actionํ์
์ Action ํ์
์ ์ ์ํ ๋ ์ฌ์ฉํ๋ RTK ํ์
์ด๋ค.
thunkํจ์๋ฅผ ์ ์ํ ๋ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ๋ฉด ๋๋ค.
export const incrementIfOdd =
(amount: number): AppThunk =>
(dispatch, getState) => {
const currentValue = selectCount(getState());
if (currentValue % 2 === 1) {
dispatch(incrementByAmount(amount));
}
};
hook.ts๋ useDispatch์ useSelector๋ฅผ ํธํ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ์ปค์คํ ํ ํ ์ ์ ์ํด์ฃผ๊ณ ์๋ค.
useAppDispatch๋ useDispatch๋ฅผ ์ด์ฉํ์ฌ dispatch๋ฅผ ์ฌ์ฉํ ๋ ์ผ์ผํ ํ์ ์ ์ง์ ํ์ง์๊ณ ํธํ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ์ง์ ํ ํ ์ด๋ค.
export const useAppDispatch = () => useDispatch<AppDispatch>()
function Counter (){
const dispatch = useAppDispatch()
return ...
<button onClick ={() => dispatch(increment())}>์ฆ๊ฐ</button>
}
useDispatch๋ฅผ ์์ฒ๋ผ ํจ์์์์ ์จ์ฃผ๋ ์ด์ ๋ ๋ฆฌ์กํธ ํ
(useDispatch)์ ํจ์์์์ ์ฌ์ฉ๋์ผํ๊ธฐ ๋๋ฌธ์ด๋ค.
Error Hook
export const useAppDispatch = useDispatch<AppDispatch>()
// React Hook "useDispatch" cannot be called at the top level.
// React Hooks must be called in a React function component or a custom React Hook function.
๋ฆฌ์กํธ ํ
๋ฆฌ์กํธ ํ ์ ์ปดํฌ๋ํธ๋ด์์ ์ฌ์ฉ๋๊ฑฐ๋ ํ ํจ์๋ด์์ ์ฌ์ฉ๋์ด์ผํ๋ค.
์ต์๋จ์ธ top-level์์ ๋จ๋ ์ผ๋ก ์ฌ์ฉํ ์ ์๋ค.
const dispatch = useDispatch()
function Counter (){
const dispatch = useAppDispatch()
return ...
<button onClick ={() => dispatch(increment())}>์ฆ๊ฐ</button>
}
ํ์ง๋ง ์์ฒ๋ผ useDispatch๋ฅผ ๊ตณ์ด ํ์ ์ ์ง์ ํด์ฃผ์ง ์์๋ ์ฌ์ฉํ ์ ์๋๋ฐ ์ ์ฌ๊ธฐ์๋ ์ปค์คํ ํ์ฌ ์ฌ์ฉํ๋ ๊ฒ์ผ๊น?
๊ณต์๋ฌธ์๋ฅผ ํ์ธํด๋ณด๋ฉด ํ์ ์ ์ง์ ํ์ฌ ์ฌ์ฉํ๋ ๊ฒ์ ๊ถ์ฅํ๊ณ ์๊ธฐ ๋๋ฌธ์ธ ๊ฒ ๊ฐ๋ค.
useAppSelector๋ useSelector๋ฅผ ์ฌ์ฉํ ๋๋ง๋ค ์ผ์ผํ ํ์ ์ ์ง์ ํ์ง์๊ณ ํธํ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ์ง์ ํ ํ ์ด๋ค.
useSelector
useSelector ํ ์ store state ์ค ์ํ๋ state๋ฅผ ๊ณจ๋ผ์ ์ฌ์ฉํ ์ ์๊ฒ ํ๋ react-redux ํ ์ด๋ค.
useSelector์ ์ธ์๋ก ํจ์๋ฅผ ํ ๋นํ๋ฏ๋ก์จ state์ ์ ๊ทผํ ์ ์๋ค.
const desiredState = useSelector(state => state.count.number)
useSelector๊ฐ ๋ฐํํ๋ ๊ฐ์ ์ฝ๋ฐฑํจ์๊ฐ ๋ฐํํ๋ ์ํ๋ state๊ฐ์ด๋ค.
import { TypedUseSelectorHook, useSelector } from "react-redux";
import type { RootState} from "./store";
export const useAppSelector : TypedUseSelectorHook<RootState> = useSelector
// ์ฌ์ฉ ์์
export type RootState = ReturnType<typeof store.getState>
const count = useAppSelector((state:RootState) => state.count.value)
useAppSelector์ TypedUseSeletorHook interface ํ์ ์ ์ง์ ํ์ฌ ์ฌ์ฉํ๋ฉด useSelector์ ํ์ ์ ์ง์ ํ ์ปค์คํ ํ ์ผ๋ก ์ฌ์ฉํ ์ ์๋ค.
export type RootState = ReturnType<typeof store.getState>
const count = useSelector((state:RootState) => state.count.value)
useDispatch์ ๋ง์ฐฌ๊ฐ์ง๋ก useSelector๋ ๊ตณ์ด ํ์ ์ ์ง์ ํด์ฃผ์ง ์์๋ ๋ด๋ถ์ ์ผ๋ก ํ์ ์ ์ถ๋ก ํ์ฌ ์ฌ์ฉํ ์ ์๋ค.
ํ์ง๋ง ๊ณต์๋ฌธ์์ ํ์ ์ ์ง์ ํ์ฌ ์ฌ์ฉํ๋ ๊ฒ์ ๊ถ์ฅํ๊ณ ์๋ค.
payload๋ ์ก์ ์์ฑํจ์ ๋ฐ ๋ฆฌ๋์์์ ์ํ๊ฐ์ ๋ฐ๊พธ๊ธฐ ์ํด ์ ๋ฌํ๋ ์ธ์์ด๋ค.
RTK์์๋ slice๋ด์์ ์ก์ ์์ฑํจ์์ ๋ฆฌ๋์๋ฅผ ๊ฐ์ด ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ reducers ๋ด์์ ํ์ด๋ก๋๊ฐ์ ์ ์ํ๊ณ ์ฌ์ฉํ ์ ์๋ค.
์ง๊ณ ๋์ด๊ฐ๊ธฐ!
RTK๋ฅผ ์ฐ์ง ์์๋๋ ์ก์ ์์ฑํจ์์ ๋ฆฌ๋์๋ฅผ ๊ตฌ๋ถํ๋ค.
์ก์ ์์ฑํจ์์ ์ธ์๋ก ํ์ด๋ก๋๊ฐ์ ์ ๋ฌํ๋ฉด ๋ฆฌ๋์์์ ํ์ด๋ก๋๊ฐ์ ์กฐํํ ์ ์์ผ๋ฉฐ ํด๋น ํ์ด๋ก๋๊ฐ์ ์ํ๊ฐ์ ๋ณ๊ฒฝํ๋๋ฐ์ ์ฌ์ฉํ ์ ์๋ค.
const textSlice = createSlice({
name: "text",
initialState,
reducers: {
minerNameChange: (state, action : PayloadAction<T>) => {
state.miner = action.payload
}
})
ํ์ด๋ก๋ ์ฝ๋(action.payload)๋ฅผ ์ ๋ ฅํ๋ค ํด๋น ์ํ๊ฐ์ ์ฌ์ฉํ ์ปดํฌ๋ํธ ๋ด์์ dispatch๋ก ์ก์ ์์ฑํจ์์ ์ธ์๋ก ์ ๋ฌํ๋ฉด payload๊ฐ์ผ๋ก ์ ๋ฌ์ด ๋๋ค.
dispatch(minerNameChange(e.target.value))
์ด ๋ payload๋ก ์ ๋ฌํ ์ธ์๊ฐ ์ฌ๋ฌ๊ฐ๋ผ๋ฉด ๋ฆฌ๋์๋ด์์ ์ด๋ป๊ฒ ์ฌ์ฉํด์ผํ ๊น?
๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ฒด๋ก ์ ๋ฌํ๋ฉด ๋๋ค.
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
const textSlice = createSlice({
name: "text",
initialState,
reducers: {
transactionInputChange: (
state,
{ // action ๊ตฌ์กฐ๋ถํด => {payload : ~~}
payload: { key, value },
}: PayloadAction<{ key: keyof textSliceInitState; value: string }>
) => {
state[key] = value;
// === state[action.payload.key] = action.payload.value
},
},
});
์์ ๊ฐ์ด payload : { a,b,c...}
๊ฐ์ฒด๋ก์จ ์ ๋ฌํ๊ณ ์ถ์ ๊ฐ์ ํ๋กํผํฐ์ ์ฌ์ฉํ๋ฉด ๋๋ค.
ํ์ ์คํฌ๋ฆฝํธ์์ payload์ ๋ํด ํ์ ์ ์ง์ ํด์ค ๋ RTK์ PayloadAction์ด๋ผ๋ ํ์ ์ ์ด์ฉํ๋ค.
import { PayloadAction } from "@reduxjs/toolkit";
๊ทธ๋ฆฌ๊ณ ๋ค์๊ณผ ๊ฐ์ด action ํ์ ์ ์ง์ ํด์ค๋ค.
const textSlice = createSlice({
name: "text",
initialState,
reducers: {
minerNameChange: (state, action : PayloadAction<string>) => {
state.miner = action.payload
}
})
PayloadAction ์ ๋๋ฆญ<>์์๋ payload์ ํ์
์ ํ ๋นํด์ฃผ๋ฉด ๋๋ค.
์์ ๊ฐ์ด payload๊ฐ์ด string์ด ๋ค์ด๊ฐ ๊ฒ ๊ฐ๋ค๋ฉด string์ผ๋ก ์ง์ ํด์ฃผ๊ณ ๊ฐ์ฒด๋ผ๋ฉด ๊ฐ์ฒด๋ฅผ ํ ๋นํด์ฃผ๋ฉด ๋๋ค.
transactionInputChange: (
state,
{
payload: { key, value },
}: PayloadAction<{ key: keyof textSliceInitState; value: string }>
) => {
state[key] = value;
PayloadAction ํ์
/*
์ฒ์ payload ํ์
์ P = void๋ก ์ค์ ๋์์ด payload๊ฐ์ด ์์ด๋ ์คํ๊ฐ๋ฅํ๊ฒ ์ค์ ๋์ด ์๋ค.
๋๋ฒ์งธ ์ ๋๋ฆญ ์ธ์๋ก T๋ action.type์ ํ์
์ผ๋ก์จ string์ผ๋ก ์ง์ ๋์ด์๋ค.
์ธ๋ฒ์งธ, ๋ค๋ฒ์งธ ์ ๋๋ฆญ ์ธ์๋ action meta , error ํ์
์ ์ง์ ํด์ค ์ ์๋ค.
*/
type PayloadAction<P = void, T extends string = string, M = never, E = never> = {
payload: P;
type: T;
} & ([M] extends [never] ? {} : {
meta: M;
}) & ([E] extends [never] ? {} : {
error: E;
})
/*
An action with a string type and an associated payload.
This is the type of action returned by createAction() action creators.
@template P โ The type of the action's payload.
@template T โ the type used for the action type.
@template M โ The type of the action's meta (optional)
@template E โ The type of the action's error (optional)
*/