Redux와 React

김민성·2021년 10월 20일
0

React만 잘하면 되는줄알았는데 Redux를 쓰면 상태관리하기 더 편하다고??
공부해보자. 정리안하면 머리에 남지않아서 나와같은 뉴비에게 도움이 되면 좋겠고, 나에게 특히 도움이 되기위해 Redux를 공부해보았다.

먼저 Redux에 대해 알아보고 추가로 React-Redux에 대해서도 알아보자

Redux가 필요한 이유

1. 컴포넌트가 많아질수록 더 이득이다.
React에서 컴포넌트를 생성하여 상태관리를 할때 가장 의문점이 컸었던것이 컴포넌트가 엄청많아지면 이 State들 관리 어떻게하지?? Props는? 어떻게함? 이라고 생각했었는데 Redux는 Store.js에서 State를 중앙집권 관리함으로써 이부분을 해결해준다

2. 버전관리가 가능하다
이부분이 진짜 놀란부분인데 확장 툴을 이용해서 동영상처럼 State의 변화흐름을 보여주고 그당시로 돌아갈수 있는 기능도 있더라. 참으로 꿀이아닐수가 없다.
이를 위해 State를 바로 변경하지 않고 복제하여 새로운 state를 만든다.
Object.assign 을 사용하거나 spread operator {...state} 를 사용하여 복제후 업데이트 한다.(하기 예시 참고)

  • 이 방식을 통해 개발자 도구에서 앞/뒤로 state에 대해 타임머신처럼 왔다갔다 할 수 있다.
  • React에서 state-setState로 변경해주는것처럼 불변성을 유지한다.
if (action.type === 'INCREMENT') {
    return { ...state, number: state.number + action.size };
  }

Redux 전체흐름

아래의 전체 흐름을 이해하는것이 중요한것같다. 이후 각각의 단어들이 의미하는것을 알아보고 어떤역할을 하는지, 어떤방식으로 작동하는지 알아보자.

큰흐름

  1. Store라는 공간을 만든후 State(객체)를 보관한다.
  2. UI에서 사용자가 어떤 행위를 한다.
  3. Dispactch에서 Action을 Reducer에 전달
  4. Reducer는 Action의 Type에 따라 실행할 명령문을 정한다.
  5. Store에 있는 state값을 대체하는것이 아니라, 복제하여 새로운 state값을 store에 저장한다.
  6. Store에 변경된 state값이 저장되면, Dispatch가 subscribe(render)를 호출해 render에게 새로운 state값을 전달함으로써 렌더링이 된다.

Store

  • 스토어는 State를 보관하는 곳이다.
  • getstate()를 통해 상태에 접근하게 하고
  • dispatch(action)를 통해 상태를 수정할 수 있게 하고
  • subscribe(listener)를 통해 리스너를 등록한다.
  • 어플리케이션당 1개만 만들 수 있다.
  • 외부에서 바로 Store로 접근하여 State를 가져갈수없으며 Reducer를 통해 접근한다.
import { createStore } from 'redux';

export default createStore(function (state, action) {
  return state;
    //store 생성, Reducer 생성, reducer는 항상 state를 반환함//
}

createStore를 통해 스토어를 생성하여 State를 보관/관리한다.

// 초기 상태를 기록
console.log(store.getState());

// 상태가 바뀔때마다 기록
let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
  )

Reducer

  • Reducer는 Action을 받아서 state의 변화를 일으키는 함수이다.
  • 인자로는 항상 현state, action을 받으며 Action에 따라 새로운 state를 return 한다. (이전의 State는 절대 건들지 않는다)
  • 순수한 함수를 사용하기(Date.now()나 Math.random() 같이 랜덤한 함수 호출X)
  • API 호출이나 라우팅 전환같은 사이드이펙트를 일으키기 않기

초기 State 설정

Case 1

function todoApp(state, action) { 
if (typeof state === 'undefined') {  
return initialState  }  
// 지금은 아무 액션도 다루지 않고  // 주어진 상태를 그대로 반환합니다. 
return state
}

Case 2
ES6 default arguments 문법을 이용해 더 간단하게 쓰는법
-> No value/ undefined값은 패스(무시된다)

function todoApp(state = initialState, action) { 
return state
}

Reducer 예시

function todoApp(state = initialState, action) {
  switch (action.type) {
  case SET_VISIBILITY_FILTER:
    return Object.assign({}, state, {
      visibilityFilter: action.filter
    });
  default:
    return state
  }
}

위 코드는 현State를 받아 switch문을 활용/ State를 복제하여 Action에 따라 새로운 State를 반영하는 코드이다.

유의사항

  • Object.assign(state, { visibilityFilter: action.filter })라고 사용해도 틀림. State가 변경되기 때문, 첫번째는 무조건 빈 객체가 들어가야한다.
  • ...state로 작성해도 가능!(spread operator) *출처참고
  • 알수없는 액션에 한해서는 default로 원래의 값을 반환해야한다.

Dispatch

  • Action이 발생시 Reducer를 호출해 State의 변화를 이끄는 함수이다.

하기와 같이 Action을 인자로 가지고 action내에는 type(필수), 반환할 값(size)가 들어간다.
Type에는 어떤역할을 하는지 표기한다. 클래스명 처럼 알아먹기 좋게하기.

store.dispatch({ type: 'INCREMENT', size: size });

Action Creater

  • 파라미터를 받아와서 액션 객체 형태로 액션 함수를 만듦.
  • 직접 액션객체를 만들어도 됨.
export function addTodo(text) {
  return {
    type: "ADD_TODO",
    text
  };
}

//text를 인자로하는 Action Creater//

dispatch(addTodo(text))
//액션을 담아서 dispatch 호출함//

//아니면 자동으로 액션을 보내주는 바인드된 액션 생산자를 만들수 있음//
const boundAddTodo = (text) => dispatch(addTodo(text))

//바로 호출가능//
boundAddTodo(text)

Subscribe

  • Store의 내장함수중 하나임.
  • 함수형태를 파라미터로 받음.
  • Action이 Dispatch 될때마다 전달해준 함수가 호출됨.
  • React-Redux(라이브러리)에서는 Connect함수를 이용해 구독함

Module 만들기 예시

*Module : Action Type, Action creater, Reducer를 포함한 파일

/* 액션 타입 만들기 */
// Ducks 패턴을 따를땐 액션의 이름에 접두사를 넣어주세요.
// 이렇게 하면 다른 모듈과 액션 이름이 중복되는 것을 방지 할 수 있습니다.
const SET_DIFF = 'counter/SET_DIFF';
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

/* 액션 생성함수 만들기 */
// 액션 생성함수를 만들고 export 키워드를 사용해서 내보내주세요.
export const setDiff = diff => ({ type: SET_DIFF, diff });
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });

/* 초기 상태 선언 */
const initialState = {
  number: 0,
  diff: 1
};

/* 리듀서 선언 */
// 리듀서는 export default 로 내보내주세요.
export default function counter(state = initialState, action) {
  switch (action.type) {
    case SET_DIFF:
      return {
        ...state,
        diff: action.diff
      };
    case INCREASE:
      return {
        ...state,
        number: state.number + state.diff
      };
    case DECREASE:
      return {
        ...state,
        number: state.number - state.diff
      };
    default:
      return state;
  }
}

Reducer가 2개이상인 경우

한 프로젝트에 여러개의 리듀서가 있는경우 Root reducer(합쳐진 Reducer)를 이용한다.
리듀서를 합치는 작업은 리덕스에 내장되어있는 combineReducers라는 함수를 사용합니다.

Root Reducer 만들기

import { combineReducers } from 'redux';
import counter from './counter';
import todos from './todos';

const rootReducer = combineReducers({
  counter,
  todos
});

export default rootReducer;

Store에 적용하기

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore } from 'redux';
import rootReducer from './modules';

const store = createStore(rootReducer); // 스토어를 만듭니다.
console.log(store.getState()); // 스토어의 상태를 확인해봅시다.

ReactDOM.render(<App />, document.getElementById('root'));

serviceWorker.unregister();

Redux야~ 편하게 해줘서 고마워~

React-Redux

나는 리액트 사용자이니 리액트에 적용하여 어떻게 사용할것인가에 대해 알아보았다.

Presentational ,Container 컴포넌트

Redux용 React 바인딩은 Presentational, Container 컴포넌트를 분리하여 사용하는것으로 채택했다. 두가지 컴포넌트의 특성부터 알아보자

Presentational 컴포넌트는 UI를 담당하며, Data 작업에는 관여하지 않는다.
Container 컴포넌트는 어떻게 동작할지 데이터 작업에 관여한다. React-Redux에서는 Connect()함수를 이용하여 Container를 생성한다.

Connect 함수

Presentational 함수와 Redux를 연결해주기 위해 Container 함수를 만들어야 한다. 직접 작성할 수도 있지만 React redux 라이브러리에 내장된 Connect() 함수를 사용하여 Container 컴포넌트를 생성하는것을 공식문서에서 추천한다. --> 쓸데없는 렌더링을 막아주기 때문

Connect()에 필요한 함수

위에서말한 Connect()를 사용하기 위해서는 2가지 함수를 정의하는 것이 필요하다.

  1. mapStateToProps() : 상태관련 (ex. State(Store) --> props(component))
    이 함수에는 현재 Redux 스토어의 상태를 어떻게 변형할지, 그리고 어떤 속성을 통해 presentational 컴포넌트로 넘겨줄지를 작성한다.
const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
    case 'SHOW_ALL':
    default:
      return todos
  }
}

const mapStateToProps = state => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

VisibleTodoList 컴포넌트는 todos를 필터링해서 TodoList에 넘겨주어야 하기 때문에, state.visibilityFilter에 따라 state.todos를 필터링하는 함수를 작성하고 이 함수를 mapStateToProps로서 사용할 수 있다.

2. mapDispatchToProps(): 액션 관련
이 함수를 통해 Store에 Action을 보낼 수 있다. Dispatch()를 인자로 받아 콜백으로 이루어진 속성들을 반환하면 presentational컴포넌트에 이 속성이 주입되게 된다.

const mapDispatchToProps = dispatch => {
  return {
    onTodoClick: id => {
      dispatch(toggleTodo(id))
    }
  }
}

VisibleTodoList가 onTodoClick 속성을 TodoList에 주입하면서 onTodoClick 함수가 TOGGLE_TODO 액션을 파견하게끔 만들어주는 함수다.

Container 생성

앞서 mapStateToProps, mapDispatchToProps 함수를 생성했다면 이제 Connect()를 통해 Container(VisibleTodoList)를 생성한다.

import { connect } from 'react-redux'

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

export default VisibleTodoList

Provider

  • React-redux 라이브러리내에 앞서 만든 모든 Contatiner컴포넌트와 Store이 연동할수있게 도와주는 컨포넌트이다
  • Provider를 불러와서 연동할 컴퍼논트와 감싸주고, props로 store를 설정해주면 끝!
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {Provider} from 'react-redux';
import store from './store';

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>
, document.getElementById('root'));

출처: https://react.vlpt.us/redux/04-make-modules.html![](https://velog.velcdn.com/images%2Fkms0206%2Fpost%2F6c913daf-d90c-414b-bbab-a0b0733fada2%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-20%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.03.15.png)
spread 연산자 : https://paperblock.tistory.com/62

profile
다양한 경험의 개발자를 꿈꾼다

0개의 댓글