Redux (ing)

이정인·2021년 12월 13일
0

TIL

목록 보기
4/7

리덕스가 대체 뭐냐!
여기 저기 긁어와서 정리하는 글
근데 공식 문서와 예제에 설명이 정말 친절하게 되어 있다.

참고:
리덕스 잘 쓰고 계시나요?
Redux 시작하기

Redux

single stored의 객체 트리 안에 global state들은 저장된다. state tree를 바꾸기 위해서는 action을 생성해야 하고 dipatch하여 저장한다. action의 구체적인 내용을 작성하기 위해서는 redux 함수를 작성해야한다.

Redux?

reducer

현재의 state값을 action 에 따라서 변경 후 새로운 state 값을 리턴해주는 함수

action : state를 어떻게 바꿀지를 설명해놓은 객체

  • type : 어떤 동작을 할지?
  • payload : 전송되는 데이터

state 객체를 변형하는 것이 아니라 state에 변화가 있을때 새로운 객체를 리턴해줘야한다.

function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case 'counter/incremented':
      return { value: state.value + 1 }
    case 'counter/decremented':
      return { value: state.value - 1 }
    default:
      return state
  }
}

store

Redux store를 만들어서 state를 저장해놓을 수 있다.

{ subscribe, dispatch, getState } 를 가진다.

  • subscribe :

    state change에 대한 응답으로 UI를 업데이트 할 때 사용

    보통 subscribe()를 바로 쓰기보다는 view binding library를 쓴다.

  • dispatch

    내부 state 값을 변경하기 위해 action을 dispatch 한다.

  • getState

// Its API is { subscribe, dispatch, getState }.
let store = createStore(counterReducer)

// The only way to mutate the internal state is to dispatch an action.
store.dispatch({ type: 'counter/incremented' })

일반적으로는 하나의 store와 하나의 root reducing 함수를 두지만 app이 커짐에따라 root reducer를 작은 reducer들로 나눠 독립적으로 state tree의 다른 부분으로 동작하게 한다.

Redux Toolkit

기존의 보일러플레이트가 귀찮았던 문제점을 해결해준 라이브러리 등장

slice

리듀서, 액션타입, 초기 상태를 하나의 함수로 편하게 선언할 수 있으며 이 4가지를 통틀어서 slice라고 부른다.

예제에 설명이 잘 되어 있다.

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { fetchCount } from './counterAPI';

const initialState = {
  value: 0,
  status: 'idle',
};

// The function below is called a thunk and allows us to perform async logic. It
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
// will call the thunk with the `dispatch` function as the first argument. Async
// code can then be executed and other actions can be dispatched. Thunks are
// typically used to make async requests.
export const incrementAsync = createAsyncThunk(
  'counter/fetchCount',
  async (amount) => {
    const response = await fetchCount(amount);
    // The value we return becomes the `fulfilled` action payload
    return response.data;
  }
);

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  // The `reducers` field lets us define reducers and generate associated actions
  reducers: {
    increment: (state) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    // Use the PayloadAction type to declare the contents of `action.payload`
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
  // The `extraReducers` field lets the slice handle actions defined elsewhere,
  // including actions generated by createAsyncThunk or in other slices.
  extraReducers: (builder) => {
    builder
      .addCase(incrementAsync.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(incrementAsync.fulfilled, (state, action) => {
        state.status = 'idle';
        state.value += action.payload;
      });
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
export const selectCount = (state) => state.counter.value;

// We can also write thunks by hand, which may contain both sync and async logic.
// Here's an example of conditionally dispatching actions based on current state.
export const incrementIfOdd = (amount) => (dispatch, getState) => {
  const currentValue = selectCount(getState());
  if (currentValue % 2 === 1) {
    dispatch(incrementByAmount(amount));
  }
};

export default counterSlice.reducer;

Redux를 사용하는 이유

  1. 상태를 더욱 쉽게 예측 가능하게 하며, 유지보수에 긍정적인 효과가 있다.

    모든 상태 업데이트를 액션으로 정의하고, 액션 정보에 기반하여 리듀서에서 상태를 업데이트한다.

  2. 미들웨어

    특정 액션이 디스패치 됐을 때 상태 업데이트 외의 다른 작업들을 따로 처리 할 수 있다.

    비동기 작업에 대한 플로우에 대하여더 많은 컨트롤을 필요로 할때 유용하다.

  3. 서버 사이드

    API 요청 결과를 사용하여 서버사이드 렌더링을 하는 것이 용이하다.

  4. 더 쉬운 테스팅

  5. 컴포넌트가 아닌 곳에서 글로벌 상태를 사용하거나 업데이트할 때 유용하다.

0개의 댓글