[21장] 타입스크립트 in React (Redux)
2022. 4. 16. 17:09ㆍ책/리액트를 다루는 기술
리덕스에 타입스크립트를 고-급지게 넣는 방법을 알아보자. 리덕스의 흐름은 아래와 같다.
- 액션 타입을 선언하고 / 액션 생성 함수를 정의한 뒤 / 액션을 받아서 실제로 작업해주는 리듀서를 작성한다
- 리듀서가 여러개라면 루트리듀서라는 이름 하에 하나로 통합해준다 (combineReducers)
- 상태를 관리해줄 스토어를 선언한다 (createStore)
- 스토어를 뿌려준다 (Provide)
- 리덕스와 연동할 컨테이너를 만들고 스토어와 연결해서 state와 dispatch를 가져온다(useSelector, useDispatch)
- 가져온걸로 지지고볶고 한다
1. 카운트
1) 액션 및 리듀서 정의(modules/counter.ts)
// 액션 타입
const INCREASE = 'counter/INCREASE' as const; // as const로 assertion 하는 이유는
const DECREASE = 'counter/DECREASE' as const; // 비록 const로 선언한 변수일 지라도
const INCREASE_BY = 'counter/INCREASE_BY' as const; // 객체안에 넣으면 타입추론은 값을 기준으로 처리되기 때문
ex) const a = 'sexy' ← 얘는 type이 sexy인 변수이나
const obj = { '우흥' : a } ← obj의 '우흥' type은 string으로 추론됨
// 액션 생성 함수
export function increase() { // 그러니까 위에서 as const를 써준 이유는
return { type: INCREASE }; // 이 객체에서 type이 string으로 추론되길 원한게 아니라
} // 'counter/INCREASE' ← 이 자체가 type이 되길 원한다는 거임
export function decrease() {
return { type: DECREASE };
}
export function increaseBy(diff :number) {
return {
type: INCREASE_BY,
payload: diff
};
}
// 관리될 상태
type CounterState = { count :number };
const initialState :CounterState = {
count: 0
};
type CounterAction =
| ReturnType<typeof increase> // ReturnType<T> : 함수의 반환값이 가지는 type을 퉤 뱉어줌
| ReturnType<typeof decrease> // 그러니까 지금 얘는 { type: DECREASE } 를 뱉어낼거임
| ReturnType<typeof increaseBy>;
// 리듀서
function counter(
state :CounterState = initialState,
action :CounterAction) {
switch (action.type) {
case INCREASE:
return { count: state.count + 1 };
case DECREASE:
return { count: state.count -1 };
case INCREASE_BY:
return { count: state.count + action.payload }
default:
return state;
}
};
export default counter;
2) 리듀서 통합(modules/index.ts)
import { combineReducers } from "redux";
import counter from './counter';
const rootReducer = combineReducers({
counter,
});
export default rootReducer;
export type RootState = ReturnType<typeof rootReducer>;
// rootReducer의 type을 굳이 따로 뽑아내서 export까지 시켜주는 이유는
// 나중에 컨테이너에서 state의 타입을 지정할 때 필요하기 때문
// 좀만 내려보면 나올거임
// 근데 rootReducer 함수가 반환하는 타입이 뭐냐구요?
// 어렵게 생각할거 없음.
// 리듀서란게 결국 액션 종류에따라 state를 조금씩 바꿔서 퉤 뱉어주잖아?
// 그러니까 좀 변하긴 했어도 결국은 state를 뱉어내는 함수가 리듀서인거고
// 그 리듀서들을 하나로 합친것뿐 본질이 변한건 아니기 때문에 결국 리듀서마냥
// 좀 변한 state를 내뱉어주는 것 뿐임
3) 스토어 생성 / 배포
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createStore } from 'redux';
import rootReducer from './modules';
import { Provider } from 'react-redux';
const store = createStore(rootReducer);
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>
);
reportWebVitals();
4) 컨테이너 연결
import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../modules";
import { increase, decrease, increaseBy } from '../modules/counter';
import Counter from "../components/Counter";
function CounterContainer() {
// 바로 여기 state꺼내올 때 아까 타입선언한 RootState가 들어가는거임
const count = useSelector((state :RootState) => state.counter.count);
const dispatch = useDispatch();
const onIncrease = useCallback(() => dispatch(increase()), [dispatch]);
const onDecrease = useCallback(() => dispatch(decrease()), [dispatch]);
const onIncreaseBy = useCallback((diff :number) => dispatch(increaseBy(diff)), [dispatch]);
return(
<Counter
count={count}
onIncrease={onIncrease}
onDecrease={onDecrease}
onIncreaseBy={onIncreaseBy}
/>
);
}
export default CounterContainer;
2. Todo리스트 (도대체 왜 모든 강의는 todo 리스트를 못해서 안달임?)
모든 코드를 다 작성하진 않겠음. 중요한건 리덕스의 흐름이고, 이것만 알면 필요한 부분은 직접 스스로 생각해보고 구현해보길 바람.
1) 액션 및 리듀서 정의
// 초기상태
type Todo = {
id :number,
text :string,
done :boolean
};
const initialState :Todo[] = [];
let nextId = 1;
// 액션 타입
const TODO_ADD = 'todo/ADD' as const;
const TODO_TOGGLE = 'todo/TOGGLE' as const;
const TODO_DELETE = 'todo/DELETE' as const;
// 액션 생성 함수
type ActionTodo =
| ReturnType<typeof addTodo>
| ReturnType<typeof toggleTodo>
| ReturnType<typeof deleteTodo>
export const addTodo = (input :string) => ({
type: TODO_ADD,
payload: {
id: nextId++,
input
}
});
export const toggleTodo = (id :number) => ({
type: TODO_TOGGLE,
payload: id
})
export const deleteTodo = (id :number) => ({
type: TODO_DELETE,
payload: id
});
// 리듀서
function todo(
state :Todo[] = initialState,
action :ActionTodo) :Todo[]
{
switch (action.type) {
case TODO_ADD:
return [...state, {
id: action.payload.id,
text: action.payload.input,
done: false
}];
case TODO_TOGGLE:
return state.map(todo =>
todo.id === action.payload
? { ...todo, done: !todo.done }
: todo);
case TODO_DELETE:
return state.filter(todo => todo.id !== action.payload);
default:
return state;
}
};
export default todo;
2) 리듀서 통합
import { combineReducers } from "redux";
import counter from './counter';
import todo from "./todo";
const rootReducer = combineReducers({
counter,
todo
});
export default rootReducer;
export type RootState = ReturnType<typeof rootReducer>;
3) 스토어 생성 / 배포
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createStore } from 'redux';
import rootReducer from './modules';
import { Provider } from 'react-redux';
const store = createStore(rootReducer);
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>
);
reportWebVitals();
4) 컨테이너 연결
import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../modules";
import { addTodo, toggleTodo, deleteTodo } from "../modules/todo"
function TodoContainer() {
const todoList = useSelector((state :RootState) => state.todo);
const dispatch = useDispatch();
const onInsert = useCallback((text :string) => dispatch(addTodo(text)), [dispatch]);
const onToggle = useCallback((id :number) => dispatch(toggleTodo(id)), [dispatch]);
const onDelete = useCallback((id :number) => dispatch(deleteTodo(id)), [dispatch]);
}
export default TodoContainer;
'책 > 리액트를 다루는 기술' 카테고리의 다른 글
[22장] 백엔드 프로그래밍(Node.js & Koa프레임워크) (0) | 2022.04.17 |
---|---|
[21장] 타입스크립트 in React (Component, State) (0) | 2022.04.16 |
[20장] 서버 사이드 렌더링 (0) | 2022.04.13 |
[19장] 코드 스플리팅 (0) | 2022.04.12 |
[18장] 리덕스 미들웨어(redux-saga) (0) | 2022.04.12 |