SoYoung210 / soso-tip

🍯소소한 팁들과 정리, 버그 해결기를 모아두는 레포
24 stars 0 forks source link

Redux Pattern #15

Open SoYoung210 opened 5 years ago

SoYoung210 commented 5 years ago

Desc

하나의 리듀서를 정의하기 위해 매번 3개의 파일을 작성하는 것은 큰 피로도를 준다. (미들웨어까지) 그래서, 몇 가지 유틸을 작성하고 리듀서 부분은 하나의 파일에 전부 작성한다.

createAsyncAction

export const createAsyncAction = (type: string) =>  {
  const FETCH = `${type}/FETCH`
  const SUCCESS = `${type}/SUCCESS`
  const FAILURE = `${type}/FAILURE`

  return {
    FETCH,
    SUCCESS,
    FAILURE,
    fetch: createAction(FETCH),
    success: createAction(SUCCESS),
    failure: createAction(FAILURE),
  }
}

export type ActionType = {
  type: string;
  payload: any;
}

FETCH, SUCCESS.. 등의 액션 타입과 함께 함수 3개를 리턴한다. fetch는 (params?: any) => ({type: FETCH, payload: params}) 형태가 됩니다.

액션 타입을 맞춰주기 위해 ActionType 형태로 정의하였습니다.

createSaga

export const createSaga = (type: string, req: any) => {
  const actions = createAsyncAction(type)

  return function*(action: any) {
    const payload = (action && action.payload) || null

    yield put(startLoading(type))
    try {
      if (typeof req === 'function') {
        const res = yield call(req, payload)
        yield put(actions.success(res))
      }

      if (Array.isArray(req)) {
        const res = yield all(req.map(r => () => call(r)))
        yield put(actions.success(res))
      }
    } catch (e) {
      yield put(actions.failure(e.statusCode))
    } finally {
      yield put(finishLoading(type))
    }
  }
}

loading state 는 일괄 saga에서 관리됩니다. 반복되는 구조를 통일시켜둠으로서 중복코드를 줄일 수 있습니다.

const START_LOADING = 'loading/START_LOADING';
const FINISH_LOADING = 'loading/FINISH_LOADING';

export const startLoading = createAction(START_LOADING);
export const finishLoading = createAction(FINISH_LOADING);

const initialState = {}

const reducer = {
  [START_LOADING]: (state: any, action: any) => ({
    ...state,
    [action.payload]: true,
  }),
  [FINISH_LOADING]: (state: any, action: any) => ({
    ...state,
    [action.payload]: false,
  }),
}

export const loadingReducer = handleActions(reducer, initialState)

fyi . handleActions

switch 문 대신 handleActions 사용하기 리듀서에서 액션의 type 에 따라 다른 작업을 하기 위해서 우리는 switch문을 사용했지요. 하지만 이 방식엔 아주 중요한 결점이 한가지 있습니다. 바로, scope가 리듀서 함수로 설정되어있다는것이지요.

그렇기 때문에 서로 다른 case 에서 let 이나 const 를 통하여 변수를 선언하려고 하다보면 같은 이름이 중첩될시엔 에러가 발생합니다.

그렇다고해서 각 case 마다 함수를 정의하는건 코드를 읽기 힘들어질것이구요..

이 문제를 해결해주는것이 바로 handleActions 입니다. 이 함수를 사용하면 다음과 같은 방식으로 해결 할 수 있습니다.

const reducer = handleActions({
  INCREMENT: (state, action) => ({
    counter: state.counter + action.payload
  }),

  DECREMENT: (state, action) => ({
    counter: state.counter - action.payload
  })
}, { counter: 0 });

그래서 실사용은!

const PREFIX = actionTypes.HOME_MUSIC;

const getMusicsAction = createAsyncAction(PREFIX);

export type SebaMusicState = {
  musicList: List<Music>
  errorMessage?: string // FIXME: 
}

const initialState: SebaMusicState = {
  musicList: emptyList(),
};

const reducer = {
  [getMusicsAction.SUCCESS]: (state: SebaMusicState, action: ActionType) => ({
    ...state,
    musicList: action.payload
  }),
  [getMusicsAction.FAILURE]: (state: SebaMusicState, action: ActionType) => ({
    ...state,
    musicList: emptyList(),
    errorMessage: action.payload,
  })
}

export const homeMusicReducer = handleActions(reducer, initialState)
export const homeMusicSaga = [takeLatest(
  getMusicsAction.FETCH, 
  createSaga(PREFIX, Application.service.getCurationMusicList)
)]
export const { fetch } = getMusicsAction;

위와같은 방식으로 사용합니다.

보완되어야 할 것

  1. TypeSafe한 점이 없음.
  2. error code handling 쪽에 코드가 보완되어야 한다.

Ref

Jbee-actionUtils velopert

SoYoung210 commented 5 years ago

With Hooks

https://velog.io/@velopert/react-redux-hooks

useSelectors with TypeScript

https://github.com/piotrwitek/react-redux-typescript-guide/blob/master/playground/src/features/todos-typesafe/selectors.ts

SoYoung210 commented 5 years ago

보완되어야 할 것

  1. Type generic을 추가해주어서 타입 safe함을 올렸다.

    export const createAsyncAction = <T>(type: string) => {
    const FETCH = `${type}/FETCH`;
    const SUCCESS = `${type}/SUCCESS`;
    const FAILURE = `${type}/FAILURE`;
    
    return {
    FETCH,
    SUCCESS,
    FAILURE,
    fetch: createAction<T>(FETCH),
    success: createAction<T>(SUCCESS),
    failure: createAction<T>(FAILURE),
    };
    };

이후, ActionType을 넣어주면 된다. export const AdviceAction = createAsyncAction<IAdviceState>(ADVICE_PREFIX);