React에서 Typescript 사용하기 - 3
04 Sep 2021 | TypescriptReact에서 Typescript 사용하기 - 3
1. reducer
- 액션 타입을 정의한다
- 각 액션에 대한 인터페이스를 정의한다
-
액션이 많을 경우 type으로 묶는다
- initialState에 대한 type을 정의한다
- reducer의 매개변수인 state, action에 대한 타입을 설정하고 return type도 state와 동일하게
- Action 타입과 Action에 대한 인터페이스는 프로젝트 성격에 맞게 따로 빼서 저장할 수도 있다
// 액션타입 정의
export enum ActionType {
SEARCH_REPOSITORIES = 'search_repositories',
SEARCH_REPOSITORIES_SUCCESS = 'search_repositories_success',
SEARCH_REPOSITORIES_ERROR = 'search_repositories_error'
}
// 액션에 대한 인터페이스
interface SearchRepositoriesAction {
type: ActionType.SEARCH_REPOSITORIES;
}
interface SearchRepositoriesSuccessAction {
type: ActionType.SEARCH_REPOSITORIES_SUCCESS;
payload: string[];
}
interface SearchRepositoriesErrorAction {
type: ActionType.SEARCH_REPOSITORIES_ERROR;
payload: string;
}
// 위 3개를 하나로 묶어서 사용하면 편리
export type Action =
| SearchRepositoriesAction
| SearchRepositoriesSuccessAction
| SearchRepositoriesErrorAction;
// state에 대한 타입 정의
interface RepositoriesState {
loading: boolean;
error: string | null;
data: string[];
}
const initialState = {
loading: false,
error: null,
data: [],
};
const reducer = (
state: RepositoriesState = initialState, // state 타입 설정
action: Action // action 타입 설정
): RepositoriesState => { // retrun 타입 설정
switch (action.type) {
case ActionType.SEARCH_REPOSITORIES:
return {
loading: true,
error: null,
data: [],
}
case ActionType.SEARCH_REPOSITORIES_SUCCESS:
return {
loading: false,
error: null,
data: action.payload,
}
case ActionType.SEARCH_REPOSITORIES_ERROR:
return {
loading: false,
error: action.payload,
data: [],
}
default:
return state;
}
};
export default reducer;
2. action
- redux-thunk 사용
- dispatch의 타입은 redux의 Dispatch, 제네릭으로 들어갈 action 타입을 넣는다
import axios from 'axios';
import { Dispatch } from 'redux';
import { ActionType } from '../action-types';
import { Action } from '../actions';
export const searchRepositories = (term: string) => {
return async (dispatch: Dispatch<Action>) => {
dispatch({
type: ActionType.SEARCH_REPOSITORIES
});
try {
const { data } = await axios.get('https://registry.npmjs.org/-/v1/search', {
params: {
text: term
}
});
const names = data.objects.map((result: any) => {
return result.package.name;
});
dispatch({
type: ActionType.SEARCH_REPOSITORIES_SUCCESS,
payload: names
});
} catch (err: any) {
dispatch({
type: ActionType.SEARCH_REPOSITORIES_ERROR,
payload: err.message
});
}
};
};
3. store
- react와 같다
// app.tsx
import { createStore, applyMiddleware } from "redux";
import thunk from 'redux-thunk';
import reducers from "./reducers";
import { Provider } from 'react-redux';
import { store } from '../state';
import RepositoriesList from './RepositoriesList';
export const store = createStore(reducers, {}, applyMiddleware(thunk));
function App() {
return (
<Provider store={store}>
<div>
{/* ... */}
</div>
</Provider>
)
}
export default App;
4. useSelector
- useSelector의
state => state.{name}
을 typescript가 인식하지 못함 - react-redux 패키지의 TypedUseSelectorHook을 사용하여 custom hook을 만들 수 있다
- TypedSelectorHook의 제네릭에 RootState를 설정하는데 combineReducers에서 묶어준 reducer들을 ReturnType으로 넣어준다
import { combineReducers } from "redux";
import repositoriesReducer from "./repositoriesReducer";
const reducers = combineReducers({
repositories: repositoriesReducer,
});
export default reducers;
// RootState를 설정하여 TypedUseSelectorHook에서 사용한다
export type RootState = ReturnType<typeof reducers>;
// useTypedSelector.ts
import { useSelector, TypedUseSelectorHook } from "react-redux";
import { RootState } from "../state";
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;
// useTypedSelector 사용
import { useTypedSelector } from '../hooks/useTypedSelector';
const RepositoriesList:React.FC = () => {
const { searchRepositories } = useActions();
const { data, error, loading } = useTypedSelector(state => state.repositories);
return (
<div>
{/*...*/}
</div>
)
}
export default RepositoriesList;
5. +보너스 액션 dispatch시 편리하게 사용하기
- 액션들을 한데 묶어서 편리하게 사용할 수 있다
- redux 패키지의 bindActionCreators를 사용한다
- 이를 이용해 custom hook을 만들 수 있다
import { useDispatch } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../state'; // 액션들
export const useActions = () => {
const dispatch = useDispatch();
return bindActionCreators(actionCreators, dispatch);
// bindActionCreators 사용하면 dispatch에 액션이 담긴 함수를 반환
// { searchRepositories: dispatch(searchRepositories}
};
// 컴포넌트에서 다음과 같이 사용할 수 있다
const { searchRepositories } = useActions();
searchRepositories(value);
참고 자료
udemy - React and Typescript: Build a Portfolio Project