설치
npm create vite@latest [프로젝트명] -- --template react-ts
0단계: 컴포넌트 만들기
// components/Counter.jsx
import React from 'react';
const Counter: React.FC = () => {
const counter = 0;
return (
<div>
<h2>{counter}</h2>
<div>
<button>더하기</button>
<button>빼기</button>
</div>
</div>
);
};
export default Counter;
1단계: store 만들기
export type **RootState** = **ReturnType<typeof store.getState>**;
export type **AppDispatch** = **typeof store.dispatch**;
- RootState: Redux 스토어의 state를 나타내는 타입
- AppDispatch: Redux 액션을 dispatch하는 함수의 타입
// store/index.ts
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
reducer: {},
});
// Redux 스토어의 state를 나타내는 타입
export type **RootState** = **ReturnType<typeof store.getState>**;
// Redux 액션을 dispatch하는 함수의 타입
export type **AppDispatch** = **typeof store.dispatch**;
export default store;
2단계: 슬라이스 만들기
- Redux Toolkit의 슬라이스 예시
const slice = createSlice({
name: slice이름,
initialState: state초기값,
reducers: {
// 리듀서구현
},
});
1. state, action의 타입 정의하기
interface StateType {
// state 타입 정의
}
interface ActionType {
// action 타입 정의
}
2. 슬라이스에 정의한 타입 지정해주기
- initialState와 action에 타입을 지정해준다. 단, action의 타입은 PayloadAction<액션타입>으로 지정해야 한다.
const initialState: StateType = 초기값
// 리듀서 함수 내부
someReducer: (state, action: PayloadAction<ActionType>) => {
// code...
}
// store/counter.tx
import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
interface CounterState {
counter: number;
}
const initialState: CounterState = {
counter: 0,
}
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
// PayloadAction<number>는 dispatch 해오는 함수의 파라미터 타입을 number로 지정한다.
add: (state, action: PayloadAction<number>) => {
state.counter += action.payload;
},
sub: (state, action: PayloadAction<number>) => {
state.counter -= action.payload;
},
},
});
store의 reducer에 counterSlice에서 정의한 리듀서 함수 등록
// store/index.tx
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counter';
const store = configureStore({
reducer: {
**counter: counterReducer**, // 추가됨
},
});
export type RootState = Re6turnType<typeof store.getState>;
export type AppDidspatch = typeof store.dispatch;
export default store;
3단계: 컴포넌트에서 store의 state와 action 접근하기
1. 커스텀 훅 정의하기
자바스크립트에서는 useDispatch와 useSelector를 사용해서 컴포넌트에서 스토어에 접근
타입스크립트에서는 기존 훅에 새롭게 정의한 타입을 적용
useDispatch는 함수 형태, 리턴 타입은 리덕스 스토어의 dispatch
AppDisdpatch타입으로 정의한 타입이기 때문에 AppDispatch를 import해서 사용
export const useAppDispatch: () => AppDispatch = useDispatch;
useSelector는 useSelectorHook 타입이지만, RootState로 제네릭 타입을 지정
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
2. selectSlice 정의하기
만약 애플리케이션이 커지면서 슬라이스가 많아진다면 각 슬라이스 파일에서 해당 슬라이스를 선택하는 콜백함수를 export 해주는 것이 유지보수에 더 좋을 것이다. 이는 다음과 같이 구현할 수 있다.
// store/foo.ts
// ...
export selectFoo = state: RootState => state.foo.bar;
// store/SomeComponent.ts
// ...
const bar = useSelector(selectFoo);
위 두 단계를 적용한 예시
// hooks/index.ts
import { useDispatch, useSelector } from 'react-redux';
import type { TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from '../store';
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// store/counter.ts
import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
import type { RootState } from '../store';
interface CounterState {
counter: number;
}
const initialState: CounterState = {
counter: 0,
}
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
add: (state, action: PayloadAction<number>) => {
state.counter += action.payload;
},
sub: (state, action: PayloadAction<number>) => {
state.counter -= action.payload;
},
},
});
export const counterActions = counterSlice.actions;
**export const selectCount = (state: RootState) => state.counter.counter; // 추가**
export default counterSlice.reducer;
// components/Counter.tsx
import React from 'react';
import { useAppDispatch, useAppSelector } from '../hooks';
import { selectCount, counterActions } from '../store/counter';
const Counter: React.FC = () => {
const counter = useAppSelector(selectCount);
const dispatch = useAppDispatch();
const addHandler = () => {
dispatch(counterActions.add(10));
};
const subHandler = () => {
dispatch(counterActions.sub(10));
};
return (
<div>
<h2>{counter}</h2>
<div>
<button onClick={addHandler}>더하기</button>
<button onClick={subHandler}>빼기</button>
</div>
</div>
)
}
export default Counter;
useSelector
useDispatch
가 아닌, 타입을 지정한 커스텀 훅useAppSelector
useAppDispatch
를 사용한다.- store/counter.ts 파일에서 counterActions를 비구조화 할당해서 export 할 수도 있다.
// 이런 식으로 비구조화 할당으로도 사용가능
export const { add, sub } = counterSlice.actions;
4단계: store 제공하기
// main.tsx
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.createRoot(document.getElementById('root')!).render(
<Provider store={store}>
<App />
</Provider>,
)
'Frontend > React.js' 카테고리의 다른 글
[React]state를 업데이트를 위한 두 가지 방법 (1) | 2024.12.29 |
---|---|
[React] 여러 클래스명을 지정하는 라이브러리 classnames (1) | 2024.12.23 |
[React] 문자열로 이루어진 태그를 페이지에 반영하는 법 (0) | 2024.12.18 |
[React] useEffect, useLayoutEffect - sideEffect 훅 (0) | 2024.08.13 |
[React] 리액트에서의 타입스크립트 (0) | 2024.08.13 |