Typescript
를 공부하면서 무수히 많은 빨간 줄을 보고 있습니다.. JS에선 잘 돌아가는 코드가 TS에선 에러가 되니 당황스러웠던 적도 많고 이해가 안 되는 부분도 많았어요. 그래도 이렇게 엄격하게 체크를 해주니 칙칙한 리액트 에러페이지를 훨씬 덜 보게 되는 것 같아요. 빨리 익숙해지고 싶습니다ㅠㅠㅠ
개발자님 !! 이 객체는
NULL
일 수도 있는걸요 ?
오늘은 기본적인 React
와 Redux
를 사용한 상태관리 코드를 작성해보려고 해요. 정말정말 많이 쓰는 패턴이기에 어떤 구조로 짜야 예쁠지 공부해보도록 하겠습니다.
늘 했던 것 처럼, 카운터를 만들어봅시다!!
보통 components
폴더와 routes
폴더도 생성해주지만 간단한 카운터만 만들 것이기 때문에 store
만 생성했습니다.
Redux
의 기본 구조인 actionCreator
와 reducer
를 만들어볼게요.
먼저 액션 생성 함수입니다!!
카운터이기 때문에 증가, 감소 액션이 필요하겠네요.
src/store/actions/counterActions.ts
const INCREASE = "counterActions/INCREASE" as const;
const DECREASE = "counterActions/DECREASE" as const;
const INCREASE_BY = "counterActions/INCREASE_BY" as const;
const increase = () => ({ type: INCREASE });
const decrease = () => ({ type: DECREASE });
const increaseBy = (diff: number) => ({ type: INCREASE_BY, payload: diff });
export default { increase, decrease, increaseBy };
짚고 넘어갈 부분은 as const
이거예요!!
타입 단언 중 Const Assertion
이라는 타입스크립트 문법인데 이렇게 선언을 해주면 INCREASE
변수에 다른 문자열이 들어오면 에러를 뿜어냅니다. 말 그대로 const
로 만드는거죠!!
이렇게 string
타입이 아닌 입력된 문자열이 박히게 됩니다.
다른 것은 크게 다른 것이 없으니 리듀서로 넘어가자구요.
src/store/actions/counterReducer.ts
import counterActions from "../actions/counterActions";
type CounterAction =
| ReturnType<typeof counterActions.increase>
| ReturnType<typeof counterActions.decrease>
| ReturnType<typeof counterActions.increaseBy>;
type CounterState = {
count: number;
};
const initialState: CounterState = {
count: 0,
};
const count = (state: CounterState = initialState, action: CounterAction) => {
switch (action.type) {
case "counterActions/INCREASE":
return { count: state.count + 1 };
case "counterActions/DECREASE":
return { count: state.count - 1 };
case "counterActions/INCREASE_BY":
return { count: state.count + action.payload };
default:
return state;
}
};
export default count;
타입스크립트이기 때문에 리듀서에 들어갈 state
와 action
의 타입을 알려줘야해요.
액션부터 해보죠!! 이 친구는 type
을 포함한 객체를 받아요. 따라서 올 수 있는 객체들을 알려주는 작업이 필요할 것 같네요.
type CounterAction =
| ReturnType<typeof counterActions.increase>
| ReturnType<typeof counterActions.decrease>
| ReturnType<typeof counterActions.increaseBy>;
그 부분이 여기입니다. ReturnType<typeof FUNC>
을 사용하게 되면 FUNC
이 리턴하는 값의 타입을 가져올 수 있어요.
액션에 우리가 받을 객체들이 예쁘게 들어가게 됩니다.
상태는 더 쉬워요!! Props
인터페이스 생성할 때 처럼 관리할 값들을 넣어주면 됩니다. 그냥 : number
로 해도 되지만 앞으로 객체를 사용할 일이 더 많을 것 같아 타입을 정의해 주었어요.
그 이후는 일반 리듀서와 똑같으니, index.js
로 가서 리듀서들을 합쳐봅시다.
src/store/reducers/counterReducer.ts
import { combineReducers } from "redux";
import counter from "./counterReducer";
const rootReducer = combineReducers({ counter });
export type RootStore = ReturnType<typeof rootReducer>;
export default rootReducer;
export type RootStore = ReturnType<typeof rootReducer>;
이 부분이 정말정말 중요해요. 나중에 useSelector
같은 함수로 스토어에 접근할 때, 스토어가 무엇인지 명시해주어야 하기 때문입니다.
rootReducer
를 최상단에 넣어줍시다.
src/index.tsx
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
상태 관리 설정이 끝났습니다! 이제 Counter.tsx
컴포넌트에서 사용해보도록 할게요.
아까 중요하다고 했었던 RootStore
를 사용할 때가 왔어요. useSelector
로 스토어에 접근할 때 타입을 명시해줍시다.
/* .. */
import { RootStore } from "./store/reducers";
/* .. */
const count = useSelector((store: RootStore) => store.counter.count);
.
.
RootStore,
즉 ReturnType<typeof rootReducer>
에 의해 store
에 무엇이 있는지 알게 되었어요.
나머지는 똑같이 사용하면 되겠습니다!! 전체 코드에요.
src/Counter.tsx
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { RootStore } from "./store/reducers";
import allActions from "./store/actions";
const Counter: React.FC<{}> = () => {
const count = useSelector((store: RootStore) => store.counter.count);
const handleIncrease = () => dispatch(allActions.counterActions.increase());
const handleDecrease = () => dispatch(allActions.counterActions.decrease());
const handleIncreaseBy = (diff: number) =>
dispatch(allActions.counterActions.increaseBy(diff));
return (
<div>
<h1>{count}</h1>
<button onClick={handleIncrease}>+1</button>
<button onClick={handleDecrease}>-1</button>
<button onClick={() => handleIncreaseBy(5)}>+5</button>
</div>
);
};
export default Counter;
기본적인 타입스크립트 환경에서의 리덕스 작성법을 공부해봤어요. 이제 미들웨어를 어떻게 작성할지 찾아볼 시간이네요ㅠㅠㅠ 뭔가 코드의 제약이 걸린다는 것이 은근히 재밌네요 ㅋㅋㅋㅋㅋ 앞으로 더 열심히 공부하겠습니다!!
파이팅 🧑🏻💻