Redux를 공부해보자.. 🧐

GyungHo Go·2020년 7월 26일
0
post-thumbnail

Redux

  • 리덕스는 상태 관리 라이브러리이다. 리덕스를 사용하면 우리가 만들게 될 상태 관리 로직들을 다른 파일들로 분리 시켜서 더욱 효율적으로 관리 할 수 있고, 글로벌 상태 관리도 할 수 있다.

Context와의 차이점

  1. 미들웨어
    리덕스에는 미들웨어라는 기능이 있다. 비동기 작업을 더욱 체게적을 관리 할 수 있다.
  2. 유용한 함수와 Hooks
    connect, useSelector, useDispatch, useStore
  3. 기본적인 최적화가 이미 되어있다.
    상태를 받아와서 사용하게 될 때, 기본적인 최적화가 되어있어서 필요한 상태로 바뀔때에만 리렌더링 되는 기능이 있다.
  4. 하나의 커다란 상태
    모든 글로벌 상태를 하나의 커다란 객체에 널어서 사용하는 것이 필수이다. 그래서 매번 context를 만드는 수로로움을 없애준다.
  5. DevTools
    아주 유용한 개발자 도구가 있다. 현재 상태를 한눈에 볼 수 있고, 지금까지 어떤 변화가 있었는지 볼 수도 있고, 특정 상태로 되돌릴수도 있다.

Redux는 언제 사용할까?

애플리케이션의 모든 상태를 redux로 관리할 필요는 없지만, 다음의 경우에 해당되는 데이터는 redux로 관리하는 게 좋다.

  • 애플리케이션의 여러 곳에서 공유되는 데이터
  • 다른 페이지를 갔다가 돌아왔을 때 그 상태를 유지할 필요가 있는 데이터

예를 들어 상품 구매 시 사용자 정보 페이지에서 결제 페이지로 갔다가 뒤로 가기를 클릭한 경우, 이전에 입력했던 사용자 정보를 유지하는 게 좋으므로 redux로 관리하면 된다. 두 가지 모두 해당되지 않는 데이터는 react에서 제공하는 컴포넌트의 state로 관리하면 된다.

redux에서 사용되는 키워드

액션 Action

상태에서 어떤 변화가 필요할 때 우리는 Action을 발생한다. 이는 하나의 객체로 표현이 된다.

{
  type: "TOGGLE_VALUE",
	data: {
		id:0,
		text: "리덕스 배우기"
	}
}

Action에는 type이라는 값이 필수적으로 있어야 한다. 나중에 리덕스에서 상태를 업데이트 할 때, type을 보고 어떻게 업데이트 할지에 대한 정보를 지니고 있는 객체이다.

type 값은 필수로 있어야 하고, 다른 값은 자유롭게 넣어주면 된다 .

{
	type: "CHANGE_INPUT",
	text: "안녕하세요"
}

이런 action같은 경우를 보면, CHANGE_INPUT 이라는 액션은 input의 텍스트 값을 "안녕하세요"라는 값으로 바꾸겠다는 것을 의미한다.

액션 생성함수 Action Creator

액체 객체를 만들어주는 함수, 단순히 파라미터를 받아와서 액션 객체를 만들어 주는 함수이다.

export function addTodo(data){
	return{
		type: "ADD_TODO",
		data
	};
}

//화살표 함수로도 만들수 있다. 
export const chageInput = text => ({
	type: "CHANGE_INPUT",
	text
});

리덕스를 사용 할 때, 액션 생성 함수를 사용하는 것이 필수적이지는 않다. 다만 액션 생성함수를 만들어 놓으면 더 편하게 액션 객체를 만들 수 있다. 만약 액션생성함수를 사용하지 않으면 발생시킬 때마다 액션 객체를 작성해 주면 된다. 꼭 쓸필요는 없지만 사용하면 편하다.

리듀서 Reducer

reducerstore가 가지고 있는 state를 변화 시켜주는 함수이다. 즉, 상태를 바꿔주거나 새로운 상태를 만들어 준다. 두 가지 파라미터(state, action)를 가져오고 action의 type에 따라 state를 변화 시킨다.

function counter(state, action) {
	switch (action.type) {
		case "INCREASE":
			return state +1;
		case "DECREASE":
			return state -1;
		default:
			return state;
	}
}

action type을 가지고 action type이 무엇이냐에 따라서 다른 업데이트 작업을 한다. 리듀서에서는 불변성을 꼭 유지해주어야 한다. 그리고 default에서 기존의 state를 그대로 반환하는 형태로 작성해 주어야 한다. 리덕스를 사용할때는 여러개의 reducer를 만들고 이를 합쳐서 루트 리듀서를 만들수 있다. 그리고 루트 리듀서 안에 있는 작은 리듀서들을 서브 리듀서라고 한다.

스토어 store

리덕스를 사용하게 되면 한 어플리케이션 당 하나의 스토어가 필요하다. 스토어 안에는 현재 앱의 상태와 리듀서가 들어있고, 추가적으로 몇 가지 내장 함수가 들어있다.

디스패치 Dispatch

액션을 발생시키고, 액션을 스토어에 전달한다.

dispatch ({ type: "INCREASE" })

디스패치라는 함수는 다음과 같이 사용하고, 액션 객체를 만들어서 디스패치 파라미터로 넣어서 호출을 한다. 그렇게 호출 하게 되면 해당 액션이 리듀서로 전달하게 되서, 리듀서 함수에서 새로운 상태를 반환하게 되면 스토어의 상태가 새로워 진다.

구독 subscribe

스토어의 내장함수중 하나이다. subscribe을 호출할 때 파라미터로 특정 함수로 넣어주면 액션이 디스패치 될때마다 우리가 설정한 함수가 호출이 된다. 스토어의 상태가 업데이트가 될때마다 특정 함수를 호출할 수가 있다. 리액트에서 리덕스를 사용하게 될때 이 함수를 직접 사용하지 않는다. 대신에 리액트 리덕스 라이브러리에서 제공하는 connect 함수 useSelector hook을 사용해서 스토어 안에있는 상태가 업데이트 되면 컴포넌트가 리 렌더링되는 작업을 대신 처리해준다.

Redux의 3가지 규칙

1. store는 오직 하나만 존재한다.

  • 하나의 application에는 하나의 store가 있다. 즉 store를 한개 이상을 만들지 말것을 권장한다. 디버깅이 쉬워지는 장점도 있다.

2. state는 읽기 전용이다.

  • state는 읽기 전용이다. 불변성을 지켜주어야 한다. 리덕스에서 state의 불변성을 유지하는 것은 좋은 성능을 지켜내기 위함이다. 불변성을 지켜야만 컴포넌트들이 제대로 리렌더링 된다. state를 변경하고 싶다면, Actiondispatch해서 state를 변경해야 한다.

3. reducer는 순수 함수여야 한다.

  • reducer 함수는 이전 상태와, 액션 객체를 파라미터로 받는다. 이전의 상태는 절대 변경하지 않고, 변화를 일으킨 새로운 state 객체를 만들어 return 한다. 똑같은 파라미터로 호출된 리듀서 함수는 언제나 똑같은 결과값을 반환해야 한다. 즉 동일한 인풋이 있을때는 동일한아웃풋이 있어야 한다.

Redux Modules

액션타입, 액션생성함수, 리듀서가 모두 들어있는 자바스크립트 파일을 의미한다. 리덕스를 사용하기 위해 위 항목들은 각각 다른 파일들로 저장 할 수 있다.

  • Ducks패턴 : 액션타입, 액션생성함수, 리듀서를 한 파일에 작성하는 방법이다. 한 파일에 액션타입, 액션생성함수, 리듀서 모두 선언한다. 여기서 주의할 점은 리듀서는 export default로 내보내주고, 액션함수는 그냥 일반 export로 내보내 준다.

modules/counter.js

//Action 타입 선언
// Ducks 패턴을 사용할때는 액션 타입을 선언할때 문자열 앞에 접두사는 붙이는데, 다른 모듈과 이름이 중복되지 않게 하기 위함이다. 
const SET_DIFF = 'counter/SET_DIFF'; //몇씩 더할지 정해준다.
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

//Action creator함수 선언
export const setDiff = diff => ({ type : SET_DIFF, diff});
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });

//reducer에서 관리할 초기상태 선언 (module의 초기상태)
const initialState = {
    number : 0,
    diff : 1
};

//reducer정의
//state에는 기본값(initialState)을 설정해 주어야 한다.
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:     //자신이 처리할수 없는 action의 경우 기존 state를 return해준다.
            return state;
    }
}

modules/index.js

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

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

export default rootReducer;

redux 두개를 합쳐서 index.js 파일에 rootReducer를 만든다.

다음은 리액트 프로젝트에 리덕스를 적용해 볼것이다. 리액트 프로젝트에 리덕스를 적용하기 위해서는 패키지 하나를 설치해 주어야 한다.

$ yarn add react-redux

카운터 구현하기

src 디렉토리에 components라는 디렉토리를 만든다. components안에 presentational 컴포넌트를 만들건데 이것은 리덕스 스토어에 직접 접근하지 않고, 필요한 값 또는 함수를 props로만 받아서 사용하는 컴포넌트 이다.

conponents/Counter.js

import React from 'react';

const Counter = (number, diff, onIncrease, onDecrease, onSetDiff) => {
    const onChange = e => {
        onSetDiff(parseInt(e.target.value, 10)) //input의 값은 문자열이다. 이것을 숫자로 변환해 주기 위해 parseInt를 사용한다. 
    }
    return (
        <div>
            <h1>{number}</h1>
            <div>
            <input type="number" value= {diff} onChange={onChange} />
            <button onClick={onIncrease}>+</button>
            <button onClick={onDecrease }>-</button>
            </div>
        </div>
    );
};

export default Counter;

Counter.js 라는 presentational 컴포넌트에서는 UI를 선언하는것에 집중했다. 필요한 값이나 함수는 props로 받아와서 사용한다.

number, diff, onIncrease, onDecrease, onSetDiff 들을 props로 받아온걸 알수 있다.

container/CounterContainer.js

import React from 'react';
import Counter from "../components/Counter";
import {useSeletor, useDispatch} from "react-redux"; //상태를 조회하기 위해서 사용
import { increase, decrease, setDiff } from '../modules/counter';

const CounterContainer = () => {
    const { number, diff } = useSeletor(state => ({
        number: state.counter.number,
        diff: state.counter.diff
    }));
    const dispatch = useDispatch();
   // dispatch({ type: 'counter/INC'}) 이처럼 dispatch를 사용해서 특정 액션을 발생시킬수 있다. 
   //하지만 우리가 액션 생성함수가 있기 때문에 굳이 이것을 사용할 필요가 없다. 

   //여기서 만든 액션 생성 함수들이 호출되면 액션객체가 만들어 져서 dispatch가 된다. 
   const onIncrease = () => dispatch(increase());
   const onDecrease = () => dispatch(decrease());
   const onSetDiff = diff => dispatch(setDiff(diff));

    return (
        <Counter number = {number}
        diff = {diff}
        onIncrease = {onIncrease}
        onDecrease = {onDecrease}
        onSetDiff = {onSetDiff}
        />
        //리액트 컴포넌트에서 리덕스를 연동할때는 이와같이 useSelector, useDispatch라는 훅스를 사용한다. 
        //useSelector 는 상태를 조회하는 훅이다. 
    );
};

export default CounterContainer;

containers 컴포넌트란, 리덕스에 있는 상태를 조회하거나 액션을 dispatch할수있는 컴포넌트를 의미한다.

상태관리는 container컴포넌트에서 해준것을 알수 있다.

CounterContainer.js에서 리덕스 스토어를 불러오고, 어떤 함수가 호출되면 액션 dispatch하는 작업을 수행한다.

하지만 이런식으로 컴포넌트를 UI만 선언하고, 리덕스 상태관리 해주는 식으로 구분지어서 만들 필요는 없다. 그냥 본인이 편한대로 선택해서 만들자.

Redux의 개발자 도구

redux 개발자 도구를 사용하면 현재 스토어의 상태를 개발자 도구에서 조회가능하고, 지금까지 어떤 action들이 dispatch 되었는지, 그리고 액션에 따라 상태가 어떻게 변화되어 왔는지 등등을 확인할수 있다.

리덕스 개발자 도구를 적용하기 위해서는 프로젝트에서 패키지를 설치해야 한다.

$ yarn add redux-devtools-extension

index.js

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 { createStore } from 'redux';   
import  rootReducer from "./modules"; 
import { composeWithDevTools } from "redux-devtools-extension";   // composeWithDevTools 에서 불러온다.

const store = createStore(rootReducer, composeWithDevTools()); // 해당 함수는 붙이면 끝.
console.log(store.getState());

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

참고

profile
기록하는 습관

0개의 댓글