두 손끝의 창조자

리덕스(redux) 작동 방식 및 구성 본문

react

리덕스(redux) 작동 방식 및 구성

codinglog 2023. 6. 5. 12:25

리덕스가 왜 필요한가?

리액트 컨텍스트 단점

  • 복잡한 셋텀 및 관리
  • 심하게 중첩된 jsx 코드가 나온다.
  • 성능
    테마, 인증 등 저빈도 업데이트는 괜찮지만 변경이 자주 일어나는 곳에는 유용하지 않다.

리덕스 작동방식

중앙 저장소 하나에 모두 관리한다.

컴포넌트는 저장소를 구독해서 상태가 변경될 때마다 알림을 받는다.
컴포넌트는 저장소에 상태 변경을 요청한다. 요청하는 것을 dispatch라고 하고 요청을 action이라고 함
Action은 리듀서에 전달된다.
Action은 단순한 자바스크립트 객체이다.
리듀서는 새로운 상태를 뱉어내고 스토어에 기존 상태를 변경한다.
저장소는 구독 중인 컴포넌트에 알림을 보낸다.
컴포넌트는 새로운 상태를 받아서 렌더링한다.

컴포넌트는 저장소 데이터를 변경하지 않는다.

구성

스토어는 리듀서를 가진다.
컴포넌트는 스토어를 구독한다.
컴포넌트는 액션을 디스패치한다.
리듀서는 액션을 받는다.

  • 스토어
  • 리듀서
  • 컴포넌트
  • 액션리듀서상태를 변경하는 로직
  • 리듀서는 순수한 함수*이어야 한다.
    사이드 이팩트가 없어야 한다.(http 요청, 데이터베이스 접근 등)
    즉, 동일한 입력에는 동일한 출력을 내놓아야 한다.
    리듀서는 기존 state를 완전히 대체해서 반환해야한다.
    즉, 일부 state의 속성만 반영해서 반환하면 그 값만 state에 반영이 된다.
    if (action.type === 'increment') {
       return {
           counter: state.counter + 1
       }
    }
    if (action.type === 'toggle') {
       return {
           showCounter: !state.showCounter
       }
    }
    이렇게 해서는 increment 만 동작하거나 toggle 만 동작한다. 반환되는 state는 모든 상태를 포함해야한다.
    if (action.type === 'increment') {
       return {
           counter: state.counter + 1,
           showCounter: state.showCounter
       }
    }
    if (action.type === 'toggle') {
       return {
           counter: state.counter,
           showCounter: !state.showCounter
       }
    }

주의사항

리듀서에서 원본 상태를 변경하면 절대 안된다.

const counterReducer = (state = {counter: 0}, action) => {
    if (action.type === 'increment') {
        return {
            counter: state.counter + 1
        }
    }
    state.counter = 0; // 절대 안된다.

    return state;
}
const store = createStore(counterReducer);

스토어

상태를 저장하는 저장소
createStore(리듀서) 메서드를 사용해서 스토어를 생성한다.

구독

컴포넌트가 저장소를 구독한다.
상태 변경 됐을 때 알림을 받는다.
store.subscribe(구독하는메서드) 메서드를 사용해서 구독할 수 있다.

react-redux에서는 useSelector 훅을 사용한다. 자동으로 구독을 해준다.

const counter = useSelector((state) => state.counter.counter);
const show = useSelector((state) => state.counter.showCounter);

디스패치

상태의 변경을 요청한다.
store.dispatch({type: 'increment'})
react에서는 useDispatch 훅을 사용한다.

const incrementHandler = () => {
    dispatch(counterActions.increment());
};

기존 리덕스 문제점

  • reducer 정의시 상태가 많으면 코드가 매우 길어진다.
  • 상태 변경시 기존 생태를 변경하면 안되기 때문에 자연스러운 코드가 안나온다.
    state.counter++ 라고 하고 싶지만 state = {counter: state.counter + 1} 이렇게 해야한다.

리덕스 툴킷

  • 리덕스를 좀 더 쉽게 사용할 수 있게 해준다.
  • 리덕스를 사용할 때 필요한 여러가지 라이브러리를 포함하고 있다.

리듀서

createSlice 메서드를 사용해서 리듀서를 생성한다.

const counterSlice = createSlice({
    name: 'counter',
    initialState: {counter: 0},
    reducers: {
        increment(state) {
            state.counter++;
        },
        decrement(state) {
            state.counter--;
        },
        increase(state, action) {
            state.counter = state.counter + action.payload.amount;
        }
    }
});

export const counterActions = counterSlice.actions;

export default counterSlice.reducer;

상태를 변경할 때 state.counter++ 이렇게 사용할 수 있다.
또한 상태의 일부만 변경할 수 있다. 즉, 새로운 state를 만들어 모든 속성을 복사할 필요가 없다.
payloadaction 파라미터를 통해서 접근할 수 있다.

만든 슬라이스는 reduceractions를 export 하여 recuder는 index.js 등에 설정되어 있는
스토어 생성시 사용한다.

스토어 생성

configureStore 메서드를 사용해서 스토어를 생성한다. 툴킷을 사용하더라고 여전히 단 한개의 리듀서만 사용할 수 있다.
하지만 사이트가 커지만 리듀서 또한 커지기 때문에 리듀서를 분리해야한다.
configureStore의 reducer 속성에 리듀서를 설정한다.
reducer 속성에 여러개로 분리된 리듀서 맵을 설정한다. configureStore는 이 맵을 하나의 리듀서로 합쳐준다.

const store = configureStore({
    reducer: {
        counter: counterReducer, auth: authReducer,
    },
});

export default store;

컴포넌트

actions는 컴포넌트에서 참조하여 사용한다.
action에 추가 데이터를 전달하려면 파라미터에 단순히 넣어 주면 된다.
파라미터에 넣은 값은 payload 속성에 담겨서 전달된다.

import { counterActions } from "../store/counter";

const Counter = () => {
    const dispatch = useDispatch();

    const incrementHandler = () => {
        dispatch(counterActions.increment(5)); 
    };
}
반응형
Comments