6 분 소요

enter image description here

📖 Context API + Reducer 와 Redux 특징과 비교

리액트에서 전역 상태를 관리할 때 많이 사용하는 Context API와 Redux를 사용법과 장단점 위주로 비교해 보겠습니다.






✔ Context API

React의 useState를 이용하면 지역 상태 관리를 할 수 있습니다. Props와 State는 부모 컴포넌트와 자식 컴포넌트 또는 한 컴포넌트 안에서 데이터를 다루기 위해 사용됩니다. 예제 사진과 같이 위에서 아래, 즉 한쪽으로 데이터가 흐르게 됩니다.

만약 다른 컴포넌트에서 한쪽으로 흐르고 있는 데이터를 사용하고 싶은 경우 또는 다른 컴포넌트에서 사용하고 있는 데이터를 현재의 데이터 호름에 넣고 싶은경우가 발생할수 있습니다.

React에서 데이터는 위에서 아래로 흐르게 되므로 사용하고 싶은 데이터와 이 데이터를 사용할 컴포넌트의 공통 부모 컴포넌트에 State를 만들고 사용하고자 하는 데이터를 Props를 전달하면 이 문제를 해결할 수 있습니다.

하지만 이처럼 컴포넌트 사이에 공유되는 데이터를 위해 매번 공통 부모 컴포넌트를 수정하고 하위 모든 컴포넌트에 데이터를 props로 전달하는 것은 매우 비효율적입니다. 이와 같은 문제를 해결하기 위해 React에서는 Flux라는 개념을 도입하였고 그에 걸맞은 Context API를 제공하기 시작했습니다.

Context는 부모 컴포넌트로부터 자식 컴포넌트로 전달되는 데이터의 흐름과는 상관없이 전역적인 데이터를 다룰 때 사용합니다. 전역 데이터를 Context에 저장한 후, 데이터가 필요한 컴포넌트에서 해당 데이터를 불러와 사용할 수 있습니다.

React에서 Context를 사용하기 위해서는 Context API를 사용해야 하며, Context의 Provider와 Consumer를 사용해야 합니다.

Context에 저장된 데이터를 사용하기 위해서는 공통 부모 컴포넌트에 Context의 Provider를 사용하여 데이터를 제공해야 하며, 데이터를 사용하려는 컴포넌트에서 Context의 Consumer를 사용하여 실제로 데이터를 사용합니다.



1. Context API 사용 방법

먼저 저희가 해야할 일은 pages 폴더에 About, Profile.js를 만들어서 url이 /about이면 About 컴포넌트를 랜더링하고 /profile이라면 Profile 컴포넌트를 랜더링 하겠습니다.

이 두가지 컴포넌트에서 똑같은 값을 전달 받아 사용하고 싶은 상황입니다. 지금은 2가지 컴포넌트이지만 About 과 Profile 컴포넌트에 하위 컴포넌트가 많다고 상상을 해보고 그 하위 컴포넌트에서도 값을 받아보는 상상을 해보겠습니다.


Context를 생성하기위해 store라는 폴더에 users.js라는 파일을 만들었습니다.

Context 객체를 만들 때는 createContext 함수를 사용합니다. 인자로는 해당 Context의 기본값을 전달합니다. 이 기본값은 Provider 컴포넌트를 사용하지 않았을 때만 사용됩니다. createContext를 불러오고 실행합니다. const UserContext = createContext();

UserContext에 넣고 싶은 값을 UserStore에 입력해줍니다. ` const users = { name: “simson”, job: “developer” };`

Provider 를 이용하여 context 값을 바꿀 수 있습니다. Provider 하위에 있는 모든 컴포넌트는 Provider의 value prop가 바뀔 때마다 리렌더 됩니다. value에는 값을 전달해줍니다. 그리고 {props.children} 으로 하위컴포넌트에 렌더링 해줍니다. return 부분에는 Provider를 적어줍니다.<UserContext.Provider value={users}>{props.children}</UserContext.Provider>


import React, { createContext } from "react";

export const UserContext = createContext();

function UserStore(props) {
  const users = { name: "simson", job: "developer" };
  return (
    <UserContext.Provider value={users}>{props.children}</UserContext.Provider>
  );
}

export default UserStore;


이렇게 하나의 context를 만들었습니다. 이제 값을 사용해보겠습니다. 만든 context를 특정한 컴포넌트와 연결 해주면 그 컴포넌트 하위에 있는 모든 컴포넌트에서 context에 접근이 가능합니다.

우리는 About과 Profile 컴포넌트에서 값을 받아야하기때문에 가장 위쪽에 연결해 주겠습니다.

App.js에 들어가서 가장 상위 컴포넌트를 UserStore를 가져와서 감싸줍니다.



지금 까지는 context를 만들고 사용해야할 컴포넌트의 상위 컴포넌트와 연결 시켜서 상위 컴포넌트 아래있는 모든 컴포넌트에서 사용할수 있도록 구현했습니다. 이번에는 사용해보도록 하겠습니다.

생성할때는 createContext()를 사용했지만 불러올 때는 useContext()를 사용합니다.

createContext()로 만든 UserContext를 사용해서 불러올수 있습니다.

값을 받을 컴포넌트 About에서 useContext를 가져옵니다. 그리고 만들어진 UserContext도 같이 가져옵니다.useContext() 에 인자를 UserContext로 넣어서 값을 반환 받으면 값이 전달된것을 확인할수 있습니다.

참고! https://www.youtube.com/watch?v=sqz45pnvJHg




✔ Reducer


수정중





✔ Redux

아래 사진을 보시면 몸무게를 값을 아래 글자 컴포넌트에 전달하려면 props를 통해서 전달해야합니다. 그 과정에서 메뉴 컴포넌트를 통해서 전달됩니다. 한두개의 컴포넌트면 쉽게 하지만 많은 숫자의 컴포넌트가 연결 되어있다면 한계가 있습니다. 이럴때 사용하는게 Redux입니다.




Redux 사용법

리덕스에는 actions,reducer,store를 만들면 리덕스 코드 준비는 끝입니다.

제가 먼저 구현해볼것은 버튼을 누르면 값이 +1 되고 -1가 되는 코드를 구현해보겠습니다.

먼저 src 폴더에 components폴더를 만들어서 컴포넌트를 관리해 주고 redux폴더를 만들어서 리덕스를 관리홰주겠습니다.

redux 폴더 안에는 sotre.js와 subscribers 폴더를 만들어서 관리하겠습니다.




먼저 subscribers 폴더 안에 actions.js를 만들어주겠습니다. type은 reducer.js 에서도 사용해야하기 때문에 types.js 에 따로 만들었습니다.

//actions.js
import { ADD_SUBSCRIBER, REMOVE_SUBSCRIBER } from "./types";

export const addSubscriber = () => {
  return {
    type: ADD_SUBSCRIBER,
  };
};

export const removeSubscriber = () => {
  return {
    type: REMOVE_SUBSCRIBER,
  };
};
//types.js
export const ADD_SUBSCRIBER = "ADD_SUBSCRIBER";
export const REMOVE_SUBSCRIBER = "REMOVE_SUBSCRIBER";


그 다음은 리듀서를 작성해줍니다. reducer에는 state,action 두가지 프로퍼티를 받습니다. state는 내부에서 initialState라는 초기값을 만들어주고 state의 어떠한 값도 안들어오면 initialState기본 값으로 받아 사용합니다. action은 actions.js에 작성한 action을 넘겨받아 type을 비교하여 switch문으로 헨들링을 해보겠습니다.


import { ADD_SUBSCRIBER, REMOVE_SUBSCRIBER } from "./types";

const initialState = {
  count: 370,
};

const subscribersReducer = (state = initialState, action) => {
  switch (action.type) {
    case "ADD_SUBSCRIBER":
      return {
        ...state,
        count: state.count + 1,
      };
    case "REMOVE_SUBSCRIBER":
      return {
        ...state,
        count: state.count - 1,
      };

    //default는 앞에 정의한 case 이외의 경우일 기본값으로 사용하기 위해 state를 리턴해줍니다.
    default:
      return state;
  }
};
export default subscribersReducer;



그 다음 작업은 store 생성입니다. store를 생성하기위해 createStore를 사용합니다. 인자로는 reducer를 넣어주시면됩니다.



import { createStore } from "redux";
import subscribersReducer from "./subscribers/reducer";

const store = createStore(subscribersReducer);

export default store;


store를 생성하였다면 redux를 사용하기 위한 마지막 단계입니다. App.js로 돌아가서 Provider를 이용하여 store를 전달합니다. 이제 사용해보도록 하겠습니다.


import "./App.css";
import Subscribers from "./components/Subscribers";
import { Provider } from "react-redux";
import store from "./redux/store";

function App() {
  return (
    <Provider store={store}>
      <div className="App">
        <Subscribers></Subscribers>
      </div>
    </Provider>
  );
}

export default App;



사용하고자 하는 컴포넌트에서 connect를 이용해줍니다. export default connect()(Subscribers); connect의 인자로는 함수가 들어갑니다. 리액트 공홈을 보면 const mapStateToProps=(){} 라는 컨밴션을 이용해라고 나와있습니다. 이름은 다르게 해도되지만 공홈에 있는 그대로 사용하겠습니다. 인자로는 state를 받습니다.

아래 코드 밑 사진을보면 370이라는 숫자를 받아왔습니다.



import React from "react";
import { connect } from "react-redux";

function Subscribers(props) {
  return (
    <div className="items">
      <p>구독자  : {props.count}</p>
      <button>구독하기</button>
    </div>
  );
}

const mapStateToProps = (state) => {
  return {
    count: state.count,
  };
};

export default connect(mapStateToProps)(Subscribers);



이번에는 구독하기 버튼을 눌러서 action 호출해서 reducer 값 상태변경을 해보겠습니다. 이 작업을 위해서 dispatch를 이용하겠습니다.

mapDispatchToProps()라는 컨벤션으로 에로우 펑션을 만들겠습니다.

dispatch를 인자로 받아서 props로 사용할 메서드를 작성해야합니다. addSubscriber라는 같은 이름으로 작성하겠습니다. 함수를 작성하겠습니다. 위에 불러온 addSubscriber를 실행하는 함수입니다.

그리고 connect의 두번재 인자로 넘겨줍니다.

button에 onClick 이벤트에 함수를 넣어서 클릭하면 props.addSubscriber()이 실행되게 작성해줍니다.

이젠 어플에 숫자가 클릭하면 증가하는걸 볼수있습니다.


import React from "react";
import { connect } from "react-redux";
import { addSubscriber } from "../redux/subscribers/actions";

function Subscribers(props) {
  return (
    <div className="items">
      <p>구독자  : {props.count}</p>
      <button
        onClick={() => {
          props.addSubscriber();
        }}
      >
        구독하기
      </button>
    </div>
  );
}

const mapStateToProps = (state) => {
  return {
    count: state.count,
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    addSubscriber: () => {
      dispatch(addSubscriber());
    },
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(Subscribers);




이번에는 view라는 조회수를 보여주는 reducer를 만들어보겠습니다. 이 과정에서는 store에 추가 연결 해야하는 작업이 필요합니다.


// views/actions.js
import { ADD_VIEW } from "./typeps";

const addView = () => {
  return {
    type: ADD_VIEW,
  };
};

export default addView;

// views/reducer.js
import { ADD_VIEW } from "./types";

const initialState = {
  count: 0,
};

const viewsReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_VIEW:
      return {
        ...state,
        count: state.count + 1,
      };
    default:
      return state;
  }
};

export default viewsReducer;
// views/types.js
export const ADD_VIEW = "ADD_VIEW";



다 만들었다면 subscribers,views 리듀서를 묶어줘야합니다. 저는 rootReducer.js를 따로 만들어서 combineReducers 를 이용하여 두개의 리듀서를 묶어보겠습니다.


import { combineReducers } from "redux";
import subscribersReducer from "./subscribers/reducer";
import viewsReducer from "./views/reducer";

const rootReducer = combineReducers({
  views: viewsReducer,
  subscribers: subscribersReducer,
});

export default rootReducer;



묶었다면 store.js에 createStore의 인자로 rootReducer를 넘겨줍니다.



import { createStore } from "redux";
import rootReducer from "./rootReucer";

const store = createStore(rootReducer);
export default store;


여기 까지가 2가지 리듀서를 묶는 잡업이었습니다. 여기서 한가지 문제가 발생합니다. 컴바인을 하면서 state의 구조가 조금 바뀌었기 때문입니다.

예를 들면 components 폴더의 Subscribers 컴포넌트에서 state을 콘솔 해보겠습니다.

아래 사진을 보시면 컴바인했던 views와 subscribers 두가지 프로퍼티로 구성되어있습니다.


이를 해결하기 위해서는 subscribers 컴포넌트에서는 아래 코드처럼 subscribers를 지정해줘야합니다.


const mapStateToProps = (state) => {
  return {
    count: state.subscribers.count,
  };
};

// 아래는 디스트럭처링을 통해서 더 간단하게 적었습니다.
const mapStateToProps = ({subscribers}) => {
  return {
    count: subscribers.count,
  };
};

댓글남기기