TypeScript React에 Redux 적용하기 - boostcamp-2020/Project15-C-Client-Based-Formula-Editor GitHub Wiki

TypeScript React에 Redux를 적용하기 위해서는 redux, react-redux, @types/react-redux를 설치해주어야한다.

npm i redux react-redux @types/react-redux

그리고 index.tsx에서 store를 생성해주면 된다.

index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './contexts';

const store = createStore(rootReducer); //rootReducer를 바탕으로 store를 생성한다.

ReactDOM.render( // Provider를 이용해서 App을 감싸주면 App하위의 컴포넌트 모두에서 Redux Store에 접근 할 수 있다.
  <Provider store={store}>
    <App />
  </Provider>,
document.getElementById('root')
);

그러면 이제 ./contexts 위치에 rootReducer를 생성해주면 된다.

contexts/index.ts

import { combineReducers } from 'redux';
import user from './user';

const rootReducer = combineReducers({
  user,
});

// 루트 리듀서를 내보내주세요.
export default rootReducer;

// ReturnType은 Generic 안에 typeof 함수를 넣으면 함수의 반환값를 유추해줍니다
// 추후 이 타입을 컨테이너 컴포넌트에서 불러와서 사용해야 하므로 내보내줍니다.
export type RootState = ReturnType<typeof rootReducer>;

contexts 폴더에 user폴더를 만들고 그 안에 user와 관련된 파일들을 만들어줍니다. combineReducers는 여러 리듀서들을 하나로 합쳐주는 함수입니다. 지금은 하나의 reducer이지만 여러개를 생길 때를 대비해서 사용하였습니다.

action, reducer, 그리고 typescript이기 때문에 types를 만들어줘야하는데 한 파일에 다 적는 ducks 패턴도 있지만 우리는 나눠주도록 하겠습니다. 한 파일에 적게 되면 파일이 너무 커지게 되고 점점 복잡해지게 되기 때문입니다. 그래서 user폴더 안에 index.ts, action.ts, types.ts, reducer.ts 파일을 만들어 줍니다.

contexts/user/index.ts

export * from './actions';
export { default } from './reducer';
export * from './types';

index.ts는 actions, reducer, types를 불러와 바로 내보내줍니다. 이렇게 하면 폴더이름 만으로 모두 접근할 수 있게 되기 때문에 사용하기 편해집니다.

contexts/user/types.ts

import {
  userLogin,
  userLogout,
} from './actions';

export type UserAction =
  | ReturnType<typeof userLogin>
  | ReturnType<typeof userLogout>

export interface FavoriteItem {
  userId?: number | null;
  id?: number;
  latex: string;
  title: string;
}

export interface UserState {
  loading: boolean;
  error: Error | null;
  userInfo: UserDataType;
}

export interface UserDataType {
  userId: number | null
  favoriteLists: FavoriteItem[];
}

이런식으로 여러 곳에서 쓰일 type들을 선언해줍니다. UserAction은

contexts/user/actions.ts

export const USER_LOGIN = 'user/LOGIN';  // 액션 타입
export const USER_LOGOUT = 'user/LOGOUT';

export const userLogin = (payload: number) => ({ // 액션 생성 함수
  type: USER_LOGIN,
  payload,
});
export const userLogout = () => ({
  type: USER_LOGOUT,
});

action을 바탕으로 dispatch를 하게 됩니다. 액션은 type과 payload를 가지고 있는 객체이며 type을 정의해주고 액션 생성함수를 만들어서 dispatch에 사용하는 것이 일반적입니다.

contexts/user/reducer.ts

import { UserAction, UserState } from './types';
import {
  USER_LOGIN,
  USER_LOGOUT,
} from './actions';

const initialState: UserState = {
  loading: false,
  error: null,
  userInfo: {
    userId: null,
    favoriteLists: [],
  },
};

function reducer(state: UserState = initialState, action: UserAction): UserState {
  switch (action.type) {
    case USER_LOGIN:
      return {
        ...state,
        userInfo: {
          ...state.userInfo,
          userId: action.payload,
        },
      };
    case USER_LOGOUT:
      return {
        ...state,
        userInfo: initialState.userInfo,
      };
    default:
      return state;
  }
}

export default reducer;

reducer는 위와 같이 작성할 수 있으며 action의 type에 따라 다른 state를 반환해주는 함수입니다.

이제 컴포넌트에서 react-redux에서 제공해주는 함수 useDispatch와 useSelector를 사용해서 사용하면됩니다! 예를 들면

dispatch사용

import { userLogin } from '@contexts/user';
import { useDispatch } from 'react-redux';

...
const dispatch = useDispatch();
const checkLogin = async () => {
    const token = await getToken();
    if (!token) return;
    const response = await API.post('/auth/autologin', '', {
      headers: {
        Authorization: token,
      },
    });

    const { userId } = response.data.results;
    dispatch(userLogin(userId));
  };
  useEffect(() => {
    checkLogin();
  }, []);

....
import { useSelector } from 'react-redux';
import { RootState } from '@contexts/index';

...
const { userInfo } = useSelector((state: RootState) => state.user); // state 타입을 알 수 잇게 RootState를 타입으로 넣어줘야함.

이런식으로 간편하게 사용할 수 있습니다!

결론

이렇듯 Redux는 dispatch에 action을 넣어 실행하면 reducer가 실행되고 인자로 action을 넣어줍니다. reducer는 action의 type에 따라 다른 state를 반환해주고 state를 사용한 모든 컴포넌트에 알려주는 방식인 pub sub 형태로 이루어져있습니다.

ducks 패턴으로 하는 방법도 있지만 각각의 폴더 안에 index, actions, types, reducer로 나누어서 작성하면 좀 더 가독성이 높게 작성할 수 있습니다.

dispatch 할 때는 useDispatch, state를 사용할 때는 useSelector hook을 react-redux로부터 가지고 와서 사용하면 편하게 사용할 수 있습니다.

⚠️ **GitHub.com Fallback** ⚠️