Redux - 21.05.17


Redux : 컴포넌트의 state를 관리하기 위해 필요. 다양한 프레임 워크(React, Angular, Vue JS 등)에서 사용 가능.

  • Redux 설치 방법
$ npm i redux --dev

Redux가 필요한 이유 : React(+Hooks)만 사용하여 state 관리 시 데이터의 흐름은 단방향이기 때문에 중첩 깊이가 깊어질 수록 중복 코드가 많아지고 코드가 직관적이지 않게 된다. 그러므로 Resux를 사용하여 state를 별도로 관리함으로써 이러한 복잡성을 줄여 준다.

Redux의 3가지 원칙

  • 전체 애플리케이션의 state는 단일 store에 저장
  • state는 읽기 전용 : 상태를 변경하는 유일한 방법은 무슨 일이 일어 났는지 설명하는 객체(object)인 Action을 보내는 것
  • 상태 변경은 순수 함수로 작성 : 상태 트리가 Action에 의해 어떻게 변환되는지 지정하려면 순수 reducer 함수를 작성

*순수 함수 : 주어진 input에 대해 정확히 같은 output을 리턴하는걸 의미 (input, output 과정 중 해당 함수의 외부 영향 없는 경우)

Redux 주요 개념

Redux

Store :

  • Redux의 핵심으로 Redux의 모든 부분을 총괄

  • store 안에서 어플리케이션의 state는 하나의 불변의(immutable) 객체(object)로 존재.

  • store는 action을 받는 즉시 reducer를 작동 시킨다.

  • reducer는 state를 return

createStore : store 생성 함수. 첫번째 인자로 reducer를 갖는다.
// store/index.js
import { createStore } from 'redux';

import rootReducer from '../reducers/index';

// createStore : store 생성 함수. 첫번째 인자로 reducer를 갖는다.
const store = createStore(rootReducer)

export default store;

reducer : state를 생산 (내가 직접 만든 state가 아님)

  • reducer는 state와 action 2개의 파라미터를 가진 자바스크립트 함수

  • reducer 함수는 스위치 구문을 갖는다.(if 문도 상관없음)

  • reducer는 action type에 따라서 새로운 state를 계산하고, 일치하는 action type이 없을 때는 적어도 초기 state를 리턴 할 것이다.

  • store는 action을 받는 즉시 reducer를 작동 시킨다.

  • reducer는 state를 return

  • reducer : 2개의 파라미터(state와 action)를 받는 순수 자바스크립트 함수. React에서 state는 setState로 변경하지만, Redux에서는 그렇게 할 수가 없다. Redux에서 state는 reducer를 거쳐 return한다.

// js/reducers.js

const initialState = {
    data: [];
};

// 순수 reducer 함수. 첫번째 인자로 initialState, 두번째 인자로 action
const rootReducer = (state = initialState, action) => state;
// 위 함수는 아무것도 하지 않고 initial state를 단순히 리턴 할 뿐.
// action을 추가해야한다 !

export default rootReducer;

action

redux에서 state를 바꾸는 유일한 방법은 store에 action를 보내야한다. 이를 ‘action을 dispatch한다’라고 표현한다.

immutable(불변) state를 바꾸는 방법은 state를 복사 후 새로운 데이터를 추가하여 새로운 state를 만들면 기존의 state에는 영향이 없다. 알아두어야 할 점은 action은 단지 자바스크립트 객체일 뿐이라는 것이다.

{   
    // 모든 action들은 state가 어떻게 변해야 하는지를 나타내는 type 속성 필요
    type: 'ADD_DATA',
    // reducer는 나중에 이 payload를 현재의 state에 추가한다.
    payload: {
      name: 'React Redux Tutorial',
      id: 1
    }
}
// action은 단지 자바스크립트 객체일 뿐

action creator : 모든 action을 함수로 감싸는 것은 최선의 방법이며 이러한 함수를 action creator라고 한다.

// actions/index.js

// 타입 불러오기
import { ADD_DATA } from "../constants/action-types";

// action creator : action들을 감싸는 함수
export const addData = data => ({ 
    // type 속성은 단순히 문자열(string)일 뿐이다.
    // reducer는 state를 어떻게 처리해야 하는지를 판단하기 위해 이 문자열을 사용
    // 문자열은 오타가 나거나 중복되기 쉬운 경향이 있기 때문에 action의 type을 상수로 선언하는 편이 좋다.
    // 상수로 선언하면 추후 디버깅 시 어려운 오류를 피하는데 좋다.
    type: "ADD_DATA",
    payload: data
  });
// constants/action-types.js
export const ADD_DATA = "ADD_DATA";
// type 속성은 단순히 문자열(string)일 뿐이다.
// reducer는 state를 어떻게 처리해야 하는지를 판단하기 위해 이 문자열을 사용
// 문자열은 오타가 나거나 중복되기 쉬운 경향이 있기 때문에 action의 type을 상수로 선언하는 편이 좋다.
// 상수로 선언하면 추후 디버깅 시 어려운 오류를 피하는데 좋다.
  • Reducer에 action 추가
// reducers/index.js

// action type 불러오기
import { ADD_DATA } from "..//constants/action-types";

const initialState = {
    data = [];
}

// reducer에 action 추가

// reducer는 action type에 따라서 새로운 state를 계산하고, 일치하는 action type이 없을 때는 적어도 초기 state를 리턴
const rootReducer = (state = initialState, action) {
    // action type의 값을 기준으로 switch
    // switch 문을 if 문으로 변경 가능
    switch (action.type) {
        // action type이 ADD_DATA인 경우
        case ADD_DATA:
            // state는 불변해야하므로 spread operator를 사용
            // (push 등 원본 배열을 바꾸는 method는 사용하면 안된다.)
            // state인 data의 원본을 복사하여 payload를 넣어준다.
            return { ...state,  data: [...state.data, action.payload] } ;
            // 위와 같이 작성하면 초기 state는 전혀 영향을 받지 않고 state 객체는 변경되지 않는다.
            // return 값은 초기 state의 복사본이다.
        // cast에 해당 되지 않는 경우 기본 값
        default:
            // 일치하는 action type이 없을 때는 적어도 초기 state를 리턴
            return state;
    }
};

export default rootReducer;

Redux Store Method

  • test code
// src/App.js

import React from 'react';
import './App.css';

import store from "./js/store/index";
import { addArticle } from "./js/actions/index";

// 콘솔에서 테스트해보기 위해 작성한 코드
window.store = store;
window.addArticle = addArticle;

class App extends React.Component {
render() {
    return (
    <div>
    </div>
    );
  }
}

export default App;
  • getState() : 현재 state를 return
// syntax
// store에는 초기 state가 있다.
store.getState()

store.getState() // {data: Array(0)}
  • subscribe() : state 변화 감지

subscribe은 action이 보내(dispatch)질 때마다 콜백 함수가 호출 된다. action을 dispatch한다는 것은 state를 바꾸기 위해 store에 알리는 것을 의미한다.

// syntax
store.subscribe(콜백  함수) 

store.subscribe(() => console.log('Hello Redex')) // action이 dispatch 될 때마다 콘솔에 Hello Redux 출력
  • dispatch() : state에 action 보내기(dispatch)

state를 바꾸기 위해 action을 dispatch 할 필요가 있다. action을 dispatch 하기위해서는 dispatch()를 호출하면 된다.

// state에 action 추가
store.dispatch( addData({ title: 'React Redux Tutorial', id: 1 }) )

// state 변경 확인
store.getState() // {articles: Array(1)}

Action, Reducer, Dispatch, Store를 연결 해주기 위한 방법

  • react-redux 설치
$ npm i react-redux
  1. connect parameter를 통해 mapStateToProps, mapDispatchToProps 등 의 메소드를 이용
import React from "react"; 
import { connect } from 'react-redux';
import { incrementAction, decrementAction } from "./Actions"; 

const counterSelector = state => state.counter; 

const CounterComponents = props => { 
    return ( 
        <> 
            <p> count: {props.counter} </p> 
            <button onClick={props.increment}>increment</button> 
            <button onClick={props.decrement}>decrement</button> 
        </> 
    ); 
}; 

const mapStateToProps = state => ({ counter: counterSelector(state) }); 

const mapDispatchToProps = dispatch => (
    { increment: () => dispatch(incrementAction()), 
      decrement: () => dispatch(decrementAction()) }
    ); 

const Counter = connect( mapStateToProps, mapDispatchToProps )(CounterComponents);
  1. Redux hooks : useSelector(), useDispatch() 등 (Redux hooks가 보다 최근에 나온 방법이고 비교적 사용하기 쉬움)
  • useSelector() : 컴포넌트와 state를 연결하는 역할. 컴포넌트에서 useSelector 메소드를 통해 store의 state에 접근

  • useDispatch() : Action 객체를 Reducer로 전달해주는 메소드. 컴포넌트에서 사용.

// 컴포넌트
import React from "react"; 
import { useDispatch, useSelector } from "react-redux"; 
import { incrementAction, decrementAction } from "./Actions"; 

const counterSelector = state => state.counter; 

const Counter = props => { 
    // useDispatch() : Action 객체를 Reducer로 전달해주는 메소드.
    const dispatch = useDispatch(); 
    // useSelector(state) : 컴포넌트와 state를 연결.
    const counter = useSelector(counterSelector); 
    
    return ( 
        <> 
            <p> count: {counter} </p> 
            <button onClick={dispatch(incrementAction())}>increment</button> 
            <button onClick={dispatch(decrementAction())}>decrement</button> 
        </> 
    ); 
};

참고 : https://ko.redux.js.org/ https://slee2540.tistory.com/38 https://usonkrap.github.io/2018/12/15/React-Redux-Tutorial-for-Beginners.html




© 2020.11. by creamer

Powered by CREAMer