내가 공부하려고 정리하는 Redux

jivyyyy·2020년 7월 8일
1

위코드 마지막 프로젝트를 하면서 가장 잘 익히고 싶었던 기능이 리덕스였는데 프로젝트 기한에 쫓기다 보니 제대로 공부를 못했었다. 마지막 달에 기업협업을 하면서 감사하게도 소스코드를 오픈해 주셔서 실무에서 사용되는 코드들을 공부할 수 있는 기회가 있었는데 확실히 책이나 유투브를 통해서 접했던 것 보다 훨씬 더 심도있게 리덕스를 사용하고 있었다. 네트워크 요청같은 비동기 작업을 위한 redux-saga 같은 미들웨어의 사용도 필수적인 것 같다.
우리가 맡은 작업은 인터렉션이 필요한 부분은 아니어서 처음엔 굳이 리덕스를 사용해야 하나? 싶은 생각도 들었지만 리덕스를 사용할 줄 모르면 리액트를 쓸 줄 모르는 것과 마찬가지라는 팀장님의 말씀을 듣고 리덕스/리덕스 미들웨어를 공부하고 있다. 하지만 계속해서 복습이 필요할 것 같다.

Action

store에 정보를 전달하기 위한 데이터 묶음을 말한다.

const ADD_TODO = "ADD_TODO";

Action Creator(액션 생성자)

const AddTodo = (text)=>{
  	type : ADD_TODO,
    	text
	}

Reducer

각각의 상태변화를 어떻게 시킬것인지에 대한 관리정보가 들어있으며 store와 정보를 주고받는다.
(전달된 action을 보고 어떻게 state를 update할지)
초기상태(initialState)와 switch문(optional)이 들어있다.

공식 문서에서는 위 세가지를 다른 파일로 작성하여 사용하지만 편의를 위해서 실무에서는 ducks 구조를 이용하여 위 세가지를 modules directorty 안에 한 파일에 작성하여 사용한다. 실제로도 이 방법이 훨씬 가독성이 좋아보인다.

CombineReducer

많은 리듀서들을 합쳐서 하나의 reducer로 관리할 수 있게 한다.

import {combineReducers} from 'redux',
(...)
const todoApp = combineReducers({visibilityfilter, todos});

Store

앱의 전체 상태를 저장할 수 있는 저장소이다. 생성방법은 아래와 같다.

1. CreateStore

최상위의 index.js 파일 내에서 스토어를 만들어 줄 수 있다

import {createStore} from 'redux';
import reducers from './modules';

//리듀서들을 store 안에 넣어서 서로 연결해준다.
const store = createStore(reducers, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
(...)
 //이후 render함수 아래에 전달해준다.
 <Provider store={store}>

2. getStore

스토의 내의 상태를 가져와준다.

console.log(store.getState());

3. dispatch(action)

스토어에 있는 리듀서들에게 action을 전달해준다.
각각의 리듀서들은 자신에게 맞는 action 이 들어온다면 store의 상태를 교체하게 된다.

store.dispatch(addTodo('new Todo'))

Provider

처음에는 provider가 왜 필요한지 이해하지 못했다.
provider 또한 하나의 컴포넌트로 이해하면 되는데 react로 작성된 컴포넌트들을 Provider컴포넌트 안에 넣으면 하위의 컴포넌트들이 provider를 통해서 redux store에 접근할 수 있게 되는 것이다.

import {Provider} from 'react-redux';
(...)
 <Provider store={store}>
  <App/> //이제 App컴포넌트는 store에 접근할 수 있게 된다.
  </Provider>

connect()

connect함수는 provider 컴포넌트 하위에 존재하는 컴포넌트들이 store에 접근할 수 있게 해준다.

import {connect}from 'react-redux';
(...)
 
// 데이터와 함수들이 props 로 붙은 컴포넌트 생성
const CounterListContainer = connect(
    mapStateToProps,
    mapDispatchToProps
)(CounterList);

1. mapStateToProps

connect 함수의 첫번째 인수로 들어가는 함수 혹은 객체
mapStatateProps의 첫번째 인자로는 State, 두번째 인자(optional)로는 우리가 원하는 객체를 담아서 넘겨준다.

//인자가 하나인 경우
const mapStateToProps = (state) => ({
    counters: state.get('counters')
});

//인자가 두개인 경우
const mapStateToProps=(state,ownProps)=>({
	todo : state.todos[ownProps.id]
})

//이 함수 자체가 필요없는 경우에는 connect 함수의 첫번째 인수로 null 을 입력해준다.
export default connect(null, mapToDispatch)(App);

2. mapDispatchToProps

connect 함수의 두번째 인자가 된다.
store에 접근한 컴포넌트가 store의 상태를 바꾸기 위해 dispatch를 사용할 수 있게 만들어준다.
mapDispatchToProps의 첫번째 인자로는 Redux의 dispatch 가 오게 되고 이를 통해서 store의 상태를 변경할 수 있게 된다.

const mapDispatchToProps = (dispatch) => ({
    onIncrement: (index) => dispatch(actions.increment(index)),
    onDecrement: (index) => dispatch(actions.decrement(index)),
    onSetColor: (index) => dispatch(actions.setColor({index: index, color: getRandomColor()}))
});

불변성 관리를 위한 라이브러리 immutable & immer

1. immutable

Map사용방법

아래와 같이 불러올 객체를 Map 함수로 맵핑해준다.

var Map = Immutable.Map;
(...)
// 초기 상태를 정의합니다
var data = Map({
  a: 1,
  b: 2,
  c: Map({
    d: 3,
    e: 4,
    f: 5
  })
})

toJS : 일반 객체로 변환시

data.toJS()

get : 특정 키 값을 받아올 때 사용

data.get('a') //일반 키 값
data.get('c','e') //깊숙한 키 값

set : 특정 키 값을 설정할때 사용

const newData = data.set('a',4) //a의 값을 4로 변경한다.
const newData = data.set(['c','e'],4 // 깊숙한 키 값 변경

2.immer

produce 함수 사용 방법

  • 첫번째 파라미터 : 수정하고 싶은 상태
  • 두번째 파라미터 : 어떻게 업데이트하고 싶을지 정의하는 함수
const state = {
  number: 1,
  dontChangeMe: 2
};

const nextState = produce(state, draft => {
  draft.number += 1;
});

console.log(nextState);
// { number: 2, dontChangeMe: 2 }

CreateAction

일일히 액션 생성자를 만들지 않고 자동화 시킬 수 있도록 해준다.

export const increment = (index) =>({
          type : type.INCREMENT,
          index})

//CreateAction을 이용해서 하기와 같이 간단하게 변환할 수 있다.
export const increment = createAction(type.INCREMENT)

파라미터로 전달한 값은 payload 에 일괄 저장된다.
예를 들어

export const setColor = createAction(types.SET_COLOR);
setColor({index : 5, color : 'fff'})
//위 setColor 의 파라미터들은 하기와 같이 전달된다.
{types:SET_COLOR,
  payload : {index : 5,
    	     color : 'fff'}}

다만 이 경우는 코드를 보았을때 어떤 파라미터를 필요로 하는지 알아보기 어려울 수 있으므로 주석을 잘 작성해 주는 것이 좋다.

handleActions

  • 첫번째 파라미터 : 액션에 따라 실행할 함수를 가지고 있는 객체
  • 두번째 파라미터 : 상태의 기본값
const reducer = handleActions({
[INCREMENT]:(state,action)=>({
counter : state.counter + action.payload}),
counter : 0})

// 초기 상태를 정의합니다
const initialState = Map({
    counters: List([
        Map({
            color: 'black',
            number: 0
        })
    ])
});

export default handleActions({
    [CREATE]: (state, action) => {
        const counters = state.get('counters');

        return state.set('counters', counters.push(
            Map({
                color: action.payload,
                number: 0
            })
        ))
    },
profile
나만의 속도로

0개의 댓글