TIL | Redux(리덕스) - 1

ryan·2020년 10월 25일
1

React

목록 보기
15/20
post-thumbnail

https://medium.com/@ca3rot/%EC%95%84%EB%A7%88-%EC%9D%B4%EA%B2%8C-%EC%A0%9C%EC%9D%BC-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-%EC%89%AC%EC%9A%B8%EA%B1%B8%EC%9A%94-react-redux-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%9D%98-%EC%9D%B4%ED%95%B4-1585e911a0a6

Redux

자바스크립트로 만든 애플리케이션들을 위한 예측 가능한 상태의 저장소

Redux의 특징

1. Single Source of Truth
하나의 Store를 사용한다.
2. State is Read-only
어플리케이션에서 state를 직접 변경할 수 없다. 무조건 action이 dispatch되어야 한다.
3. Changes are made with pure Functions
action 객체를 처리하는 함수를 reducer라고 부른다. reducer는 정보를 받아서 상태를 어떻게 업데이트 할 지 정의한다. reducer는 '순수 함수'로 작성되어야 한다. 즉, 비동기적인 처리를 하면 안된다. 즉, 네트워크 및 데이터베이스 접근 X, 인수 변경 X. 같은 인수로 실행된 함수는 언제나 같은 결과를 반환하고, 순수하지 않은 API는 사용할 수 없다.(Date.now(), Math.random() 등).


  • 동기(synchronous: 동시에 일어나는): 동기는 말 그대로 동시에 일어난다는 뜻이다. 요청과 그 결과가 동시에 일어난다는 약속인데, 바로 요청을 하면 시간이 얼마나 걸리던지 요청한 자리에서 결과가 주어져아 한다.
    -> 요청과 결과가 한 자리세어 동시에 일어남
    -> A노드와 B노드 사이의 작업 처리 단위(transaction)를 동시에 맞추겠다.

  • 비동기(Asynchronous: 동시에 일어나지 않는): 비동기는 동시에 일어나지 않는다를 의미한다. 요청과 결과가 동시에 일어나지 않을거라는 약속이다.
    -> 요청한 그 자리에서 결과가 주어지지 않음.
    -> 노드 사이의 작업 처리 단위를 동시에 맞추지 않아도 된다.

  • 동기와 비동기의 장단점
    -> 동기방식은 설계가 매우 간단하고 직관적이지만 결과가 주어질 때까지 아무것도 못하고 대기해야 하는 단점이 있고, 비동기방식은 동기보다 복잡하지만 결과가 주어지는데 시간이 걸리더라도 그 시간 동안 다른 작업을 할 수 있으므로 자원을 효율적으로 사용할 수 있는 장점이 있다.


Redux의 형태

import { createStore} from 'redux';

// STORE -> GLOBALISED STATE

// ACTION INCREMENT
const increment = () => {
  return {
    type: 'INCREMENT'
  }
}

// ACTION DECREMENT
const decrement = () => {
  return {
    type: 'DECREMENT'
  }
}

// REDUCER
const counter = (state = 0, action) => {
  switch (action.type) :
    case 'INCREMENT':
    	return state + 1;
    case 'DECREMENT':
    	return state - 1;
}
};
  
let store = createStore(counter);

// Display it in the console
store.subscribe(() => console.log(store.getState()));

// DISPATCH
store.dispatch(increment());
store.dispatch(decrement());

Redux Flow

store

상태가 기본적으로 전부 store에서 집중 관리 된다. 커다란 JSON의 결정체 정도라고 생각할 수 있다. 규모가 클 경우에는 상태를 카테고리별로 분류하는 경우가 일반적이다.

// 세션과 관련된 것
session: {
  loggedIn: true,
    user: {
      id: "114514",
      screenName: "@mpyw",
    },
},

// 표시 중인 타임라인에 관련된 것
timeline: {
  type: "home",
  statuses: [
    {id: 1, screenName: "@mpyw", text= "hello"},
    {id: 2, screenName: "@mpyw", text= "bye"},
    ],
  },
  
// 알림과 관련된 것
notification: [],
}

Action 및 Action Creator

StoreStore에 존재하는 State는 아주 신성한 것이다. React 컴포넌트 같은 하등한 것이 직접 접근하면 안된다. 직접 접근하기 위해서는 Action 이라는 의식을 거쳐야 하는데, 이벤트 드리븐과 같은 개념이다.
-> Store에 대해 뭔가 하고 싶은 경우엔 Action을 발행한다.
-> Store의 문지기가 Action의 발생을 감지하면, State가 경신된다.

Action은 기본적으로 아래와 같은 포멧을 가지고 있는 Object이다.

{
  type: "액션의 종류를 한번에 식별할 수 있는 문자열 혹은 심볼",
  payload: "액션의 실행에 필요한 임의의 데이터",
}

예를 들어서 카운터의 값을 2배 늘리는 경우, 아래와 같은 Object가 될 것이다. 머릿 부분에 @@myapp/ 라고 prefix를 붙인 것은 다른 사람이 쓴 코드와의 충돌을 피하기 위함이다.

{
  type: "@@myapp/ADD_VALUE",
  payload: 2,
}

그런데 매 번 이런 Object를 만드는 것을 수작업으로 하는 것은 괴로운 일이다. 그리고 매번 Action 명을 문자열로 쓰는 것도 불편하다. 그래서 이걸 편하게 하기 위해서 상수와 함수를 준비하는 것이 일반적이다. 외부 파일이 참고할수도 있으니 제대로 export 해둬야한다.

export const ADD_VALUE = '@@myapp/ADD_VALUE';
export const addValue = amount => ({type: ADD_VALUE, payload: amount});

Reducer

store의 문지기 역할이라고 생각하면 된다. 함수형 프로그래밍에서 Reducer라는 용어는 합성곱을 의미하지만, Redux에 한해서는 아래와 같이 이전 상태와 Action을 합쳐, 새로운 state를 만드는 조작을 의미한다.

import { ADD_VALUE } from './actions';

export default (state = {value: 0}, action) => {
  switch (action.type) {
    case ADD_VALUE:
      return { ...state, value: state.value + action.payload };
    default:
      return state;
  }
}
  • 초기 상태는 Reducer의 default 인수에서 정의된다.
  • 상태가 변할 때 전해진 state는 그 자체의 값으로 대체되는 것이 아니라, 새로운 것이 합성되는 것처럼 쓰여진다.

반환된 state는 store에 바로 반영 되어 아래와 같이 변화한다.

{
	value: 2,
}

트위터처럼 대규모 개발에 Reducer를 미세하게 분할하는 경우 Redux에서 제공하는 combineReducers함수를 이용하여 아래와 같이 쓴다.

import { combineReducers } from 'redux';

const sessionReducer = (state = {loggedIn: false, user: null}, payload) => {
  // 생략
};

const timelineReducer = (state = {type: "home", statuses: []}, payload) => {
  // 생략
};

const notificationReducer = (state = [], payload) => {
  // 생략
};

export default combineReducers({
  session: sessionReducer,
  timeline: timelineReducer,
  notification: notificationReducer,
})

위와 같이 하면, Reducer 분할에 쓰인 Key가 그대로 State 분할에도 쓰인다. 또한 실제로 각각의 reducer의 정의 자체도 다른 파일로 나누는 것이 일반적이다.

순수한 Component와 연결된 Component

React의 컴포넌트 자체는 Redux의 흐름에 타는 것이 불가능하다. 흐름에 타기 위해서는 ReactRedux에 의해 제공되는 connect라고 불리는 함수를 이용해서 아래와 같이 쓴다.

>>>>>>>>>> 함수형
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { addValue } from './actions';

const Counter = ({ value, dispatchAddValue }) => (
  <div>
  	Value: {value}
    <a href="#" onClick={e => dispatchAddValue(1)}>+1</a>
    <a href="#" onClick={e => dispatchAddValue(2)}>+2</a>
  </div>
  
export default connect(
  state => ({ value: state.value }),
  dispatch => ({ dispatchAddValue: amount => dispatch(addValue(amount)) }))(Counter)

>>>>>>>>>> 클래스형
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { addValue } from './actions';class Counter extends Component {
    render() {
        const { value, dispatchAddValue } = this.props;
        return (
            <div>
                Value: {value}
                <a href="#" onClick={e => dispatchAddValue(1)}>+1</a>
                <a href="#" onClick={e => dispatchAddValue(2)}>+2</a>
            </div>
        );
    }
}export default connect(
    state => ({ value: state.value }),
    dispatch => ({ dispatchAddValue: amount => dispatch(addValue(amount)) })
)(Counter)

먼저 컴포넌트가 store로부터 뭔가 정보를 받는 경우에, 그걸 props를 통해서 받는다. props는 immutable(불변의)하다. 즉, 상태가 변경될 때마다 새로운 컴포넌트가 다시 만들어진다는 것이다. 이것을 염두에 둔 후에 connect를 실행하고 있는 주변 코드를 보자.

  1. store가 가진 state를 어떻게 props에 엮을지 정한다. **(이 동작을 정의하는 함수는 mapStateToProps 라고 불린다.)
  2. Reducer에 action을 알리는 함수 dispatch를 어떻게 props에 엮을지 정한다. (이 동작을 정의하는 함수는 mapDispatchToProps 라고 불린다.)
  3. 위에 두가지가 적용된 props를 받을 컴포넌트를 정한다.
  4. Store와 Reducer를 연결시킬 수 있도록 만들어진 컴포넌트가 반환값이 된다.

connect(mapStateToProps, mapDispatchToProps)(Component)라고 쓰인걸 보면 좀 독특하다고 생각할 수 있겠지만, 결국 최종적인 반환값은 4번과 같다.

Redux(리덕스) - 2에서 계속...

profile
👨🏻‍💻☕️ 🎹🎵 🐰🎶 🛫📷

1개의 댓글

comment-user-thumbnail
2020년 10월 25일

여얼 리덕스 마스터 하신겁니까?~

답글 달기