리덕스없이 state 관리하기(hooks+context)

kimu2370·2020년 4월 9일
0

보통은 Redux를 사용하여 상태 관리를 하지만, 소규모로 프로젝트를 진행할 때는 고려해 볼 사항이다. 필요한 코드가 많기 때문이다. 오늘은 상태 관리가 필요한 이유와 리덕스없이 상태를 관리할 수 있는 방법을 정리해보려 한다.

Why you need state management

일반적으로 React에서 데이터 상태를 전달할 때 props Drilling 패턴으로 상태를 전달한다.

  • Props Drilling 패턴이란?

    위 그림처럼 React에서는 만약 App 컴포넌트에서 C 컴포넌트에게 데이터를 주고 싶을 때
    A와B에는 필요없지만 C에게 데이터를 전달하기 위해서는 상위 컴포넌트에서 프로퍼티를 마치 아래로 내려꽂듯이 데이터를 전달해줘야 한다.
    이러한 패턴을 props Drilling(프로퍼티 내려꽂기) 패턴이라고 한다.
    이 패턴은 재활용이 불가능하지만 상하 간의 의존적이게 된다. 이러한 문제를 해결하기 위해서는 전역에서 상태를 관리하는 무언가가 필요하다.

What is the Context API

새로운 Context API는 React 16.3에서부터 제공되었다. React 공식 문서에 따르면 컨텍스트를 다음과 같이 설명한다.

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

React ContextAPI는 React가 직접 연결되지 않은 여러 구성 요소에서 상태를 관리하는 방법이다.

using Context API

컨텍스트를 만들려면 createContext React의 메서드를 사용한다. 이 메서드는 기본값으로 매개 변수를 허용한다.

import React from "react"

const newContext = React.createContext({color:"black"});

createContext 메서드는 Provider 와 Consumner 컴포넌트가 포함된 객체를 리턴한다.

Provider 컴포넌트 계층 구조 내에서 얼마나 중첩되었든지, 모든 하위 컴포넌트에서 state를 사용할수 있게 한다. Provider는 value를 prop으로 전달한다. 이 value prop은 현재 우리가 value를 전달하는 곳이다.

<Provider value={color:"blue"}>
  {children} //value가 전달되는 곳.
</Provider>

Consumer는 이름 그대로, Prop Drilling을 할 필요없이 Provider로부터 데이터를 소비한다.

<Consumer>
  {value => <span>{value}</span>}
</Consumer>

여기에 Hooks의 useContext를 결합하면 좀 더 쉽게 상태 관리 문제를 해결할 수 있다.

The useContext Hook

React Context API를 설명할 때 콘텐츠({children})를 Consumer로 감싸고 상태를 액세스(또는 소비)할 수 있도록 자식으로 함수를 전달했다.

이로 인해 불필요한 컴포넌트 중첩이 발생하고 코드의 복잡성이 증가한다.

useContext는 이러한 것들을 훨씬 더 좋고 간단하게 만든다. 우리가 state에 접근하려면, 우리가 해야할 일은 생성한 context를 인자로 넣는 것 뿐이다.

const newContext = React.createContext({color:"black"});

const value = useContext(newContext);

console.log(value); //this will return {color:"black"}

이제 컨텐츠를 Consumer 컴포넌트로 래핑하지 않고, 변수를 통해 간단하게 state에 액세스 할 수 있다.

The useReducer Hook

useReducer는 React version 16.7.0에서 등장했다.자바스크립트 Array 내장 메서드 중 reduce()처럼, current state 와 action 두 개의 인자를 받고 new state를 반환한다.

const [state, dispatch] = useReducer((state, action)) => {
  const {type} = action;
  switch(action) {
    case "action description": //ex) "LOGIN"
      const newState = //해당 액션이 발생하면 새로운 상태를 만들겠다.
      return newState;
    default:
      throw new Error()
  }
}, []);

위에서 state 와 이에 대응하는 메서드인 dispatch를 정의했다. 필요한 곳에서 (예를들면, 버튼클릭) dispatch 메서드를 호출하면 useReducer() Hook은 메서드가 action 인자로 받아온 type과 일치하는 것을 실행시켜서 반환한다.

...
return(
  <button onClick={()=> dispatch({type: "action type"})}>
  </button>

The useReducer Hook plus the Context API

앞에서 createContext()ProviderConsumer 컴포넌트를 포함하는 객체를 반환한다고 했다.
우리는 Provider 컴포넌트만 사용하고, Consumer 컴포넌트 대신 상태가 필요한 곳에 useContext를 액세스 시켜줄 것이다.

store 설정

위에서 살표 본 useReducer와 context API를 결합해서 전역에서 상태를 관리하는 store를 만들어 보겠다.

import React, {createContext, useReducer} from "react";

const initialState = {};
const store = createContext(initialState);
const {Provider} = store;

const StateProvider = ({children}) => {
  const [state, dispatch] = useReducer((state,action)=>{
    switch(action.type) {
      case 'action description':
        const newState = //해당 액션이 발생하면 새로운 상태를 만들겠다.
        return newState;
      default:
        throw new Error();
    };
  },initialState);

  return <Provider value={{state,dispatch}}>{children}</Provider>;
}

export {store, StateProvider}
  • StateProvider 컴포넌트의 리턴값으로 {children}을 감싼 Provider 컴포넌트에 value prop으로 {state,dispatch}를 전달한다.
  • state와 dispatch는 각각 useReducer 메서드로 정의한 initialState와 해당 액션 타입으로 실행시키는 액션 실행 함수가 정의 되어 있다.

Accessing our state globally

redux에서 store를 사용하듯이 전역에서 store를 사용할 수 있게 최상위 root 컴포넌트를 store로 감싸야한다.

//root index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { StateProvider } from './store.js';

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

using store

// exampleComponent.js
import React, { useContext } from 'react';
import { store } from './store.js';

const ExampleComponent = () => {
  const globalState = useContext(store);
  const { dispatch } = globalState;

  dispatch({ type: 'action description' })
};
...

결론

현재 Globaleur라는 스타트업에서 기업협업 인턴으로 1달간 일을 하고 있는데, 이곳은 리덕스를 사용하지 않고 useContext와 Hook을 사용하여 state를 관리하고 있다.
2차 프로젝트 때 리덕스도 사용해봤지만 아직 뭐가 더 괜찮은지는 잘 모르겠다...(둘 다 어려움..ㅠㅠ) 필요에 따라 또는 프로젝트에 따라 적절히 사용하는 것이 좋을 것 같다.


Reference

https://velog.io/@public_danuel/trendy-react-usecontext
https://blog.logrocket.com/use-hooks-and-context-not-react-and-redux/

profile
DO WHAT YOU LOVE

0개의 댓글