Redux - 상태 관리 도구
Post

Redux - 상태 관리 도구

먼 이웃 컴포넌트에게 prop을 넘길때 prop-drilling이 발생하게 되는데, 이를 해결하기위해 global한 전역 상태 관리 도구인 Redux를 사용할 수 있다.

Flux

Redux에는 Flux의 개념이 포함되어 있다.

Flux는 facebook에서 만든 오픈소스 라이브러리이지만, 현재는 사용하지 않고있다.

action, dispatcher, store, view 등으로 구성되어 있다.

action -> dispatcher -> store -> view -> action 순으로 반복된다.

dispatcher

모든 데이터의 흐름을 관리하는 중앙 허브의 역할을 한다.

store로의 접근이 가능하다

store

상태 저장소이며, 상태를 업데이트 할 수 있는 함수를 제공한다. 변경 후 view로 전달하고 이후에 view가 갱신된다.

Redux

Redux는 Flux + Reducer 의 개념이 합쳐진 도구이다.

reducer

action과 마지막 store의 상태를 기준으로 새로운 상태를 만들어 준다.

store의 내장 함수인 dispatch를 사용하여 reducer로 전달한다.

규칙

  • single source of truth
    • 애플리케이션의 모든 상태는 하나의 저장소 안에 저장해야한다.
    • 여러개일 경우 충돌 혹은 동기화에 대한 이슈가 발생한다.
    • 디버깅과 생산성 향상의 이점을 가지고 있다.
  • state is read-only
    • 상태는 읽기만 허용된다.
    • 액션을 통해서만 변경이 가능하다.
    • 변화의 의도를 파악하고 중앙에서 흐름관리를 엄격하게 하기 위함이다.
  • Changes are made with pure functions
    • 변화는 순수함수로만 해야한다.
    • 순수함수 : 외부값에 의존하지않고 매개변수만을 통해 반환값을 만들어 내는 함수

middleware

store.dispatch함수의 실행 뒤애 어떠한 작업을 하기위해 호출한다.

thunk, saga, logger가 middleware로 많이 사용된다.

redux logger

  • prev state와 next state, action등을 나열해 보여준다
  • 디버깅을 위해 사용된다

redux-thunk

  • thunk : 특정작업을 나중에 하기위해 만들어준 함수이다
  • 비동기 작업을 처리할 때 가장많이 사용한다
  • 객체대신 함수를 dispatch할 수 있게 해준다
  • 낮은 boilerplate와, 이해하기 쉬운 코드가 장점이다
  • 서비스가 커질경우 잘못다뤄지면 수없이 많은 콜백 지옥에 빠질 수 있다

redux-saga

  • action의 발생여부를 모니터링하다가, 그 뒤 작업을 진행 하도록 한다
  • thunk에서 못하는 비동기작업중 기존요청취소, 특정액션 발생시 다른액션 dispatch되는 등의 작업을 할 수 있다
  • es6의 제너레이터 문법을 사용해야하므로 러닝커브가 높다
  • 초기에 구현해야할 boilerplate가 많다
  • 순수함수로 작성되므로 테스트 적용이 쉽다

redux-thunkredux-sage둘다 비동기 처리를 위한 것으로, 상황에 맞게 사용하면된다.

boilerplate

불필요하고 복잡한, 반복되는 코드 작성을 줄이기 위한 일을 boilerplate라고 한다.

redux에서 자주 사용하는 hook

  • useSelector : store에 있는 값을 가져오기위해 사용한다
  • createSelector : reselect package에 있는 함수이고, memorization 등 캐싱을 하기위해 사용한다

사용

npm install —save redux react-redux

src내부에 actions, reducer, store 폴더를 각각 생성하여 사용하는 게 일반적이다.

  1. reducer폴더에서 initialState와 순수함수 작성
  2. actions폴더에서 액션을 정의한 후, 액션함수 작성
  3. reducer의 순수함수에서 switch로 정의한 액션마다 state가 어떻게 변할지 정의
  4. store폴더에서 rootReducer를 combineReducers로 설정

    • combineReducers는 여러 리듀서들을 합칠 수 있다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    import {combineReducers, createStore} from 'reudx';
    import {countReducer} from '../reducers/count';
    
     const rootReducer = combineReducers({
     count : countReducer,
     //리듀서 추가될때마다 위처럼작성
     })
    
     const store = createStore(rootReducer);
    
     export default store;
    
  5. 최상위 파일(ex. App.js)에서 provider 작성

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    import store from './src/store/store';
    import {Provider} from 'react-redux';
    import {CounterScreen} from './src/screens/CounterScreen';
    
    export default function App(){
        return(
            <SafeAreaProvider>
                <Provider store={store}>
                    <CounterScreen/>
                </Provider>
            </SafeAreaProvider>
        );
    }
    
  6. 디버깅을 위해 redux-logger 사용 npm install —save redux-logger

    store 폴더에서 logger를 applyMiddleware([logger]))처럼 작성할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    import {combineReducers, createStore} from 'reudx';
     import {countReducer} from '../reducers/count';
     import logger from 'redux-logger';
    
     const rootReducer = combineReducers({
         count : countReducer,
         dayCount : dayCountReducer,
         //리듀서 추가될때마다 위처럼작성
     })
    
     const store = createStore(rootReducer, applyMiddleware([logger]));
    
     export default store;
    
    
  7. redux가 필요한 컴포넌트에서 dispatch하기

    • useDispatch()useSelector() hook을 이용해서 redux의 값을 사용한다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    import {useDispatch, useSelector} from 'react-redux';
    import {deleteCount } from '../actions/counter';
    
    export default () => {
        const dispatch = useDispatch();
        const value = useSelector((state)=>state.count.count);
    
        const onPressMinus = useCallback(() => {
            dispatch(deleteCount);
        },[]);
    
        return(
            <View>{value}</View>
        )
    }