웹/React

상태관리 맛보기

한땀코딩 2020. 11. 29. 22:18

신나는 프론트엔드!!

(내 현재 상황 요약)

바닐라 자바스크립트로만 코딩을 하다가 리액트를 처음 접했을 때는 엄청난 신세계를 접한 기분이었습니다. 말로만 듣던(?) 컴포넌트를 활용해서 코딩을 해볼 수 있고, 복잡하게 이벤트리스너와 핸들러를 구현할 필요도 없어 보였습니다. 이런 느낌이면 할만하겠는데!라고 생각이 살짝 올라올 때, 슬슬 그것이 찾아왔습니다.

상태 관리

아주 간단한 페이지 구성을 하는데도 props가 미친 듯이 늘어나기 시작하고, 가장 하위 컴포넌트에게 전달하려고 10개가 넘어가는 props를 몇 번에 걸쳐 전달하는 제 코드를 발견하게 되었습니다. 깃허브 이슈 탭 클론 코딩을 할 때였는데, 결국 보다 못한 팀원 분이 Context API와 useReducer를 이용해 제 코드를 완전 해체하고 조립해주셨습니다. 옆에서 구경만 하고 있던 입장에서 큰 틀의 로직은 이해가 갔지만 이걸 과연 내가 스스로 적용해볼 수는 있을까, 하는 걱정도 앞섰던 거 같습니다.

어쨌든 리액트로 (혹은 다른 프론트엔드 프레임워크도 비슷하겠죠) 작업을 하게 되면 정말 단일 페이지의 한 두 개의 컴포넌트에서 끝나는 게 아니라면 상태 관리는 필수입니다. 특히 확장성을 고려한다면 더더욱 그런 것 같습니다. 현재 프로젝트에서도 상태 관리를 위해 어떤 툴을 활용할지를 계속 고민하고 있는데, 의외로 베스트 프랙티스나 must가 없는 느낌이라 더 답답하기도 합니다.

어떤 선택지가 있는가?

2020년 말 기준, 알아본 바로는 대표적으로 네 가지의 선택지가 언급되고 있습니다.

  1. Redux
  2. Context API + useReducer
  3. Recoil
  4. MobX

(가볍게 맛보려고 했는데... 상태 관리 툴을 누가 상태 관리 해줬으면 좋겠다...)

점유율, 혹은 현존하는 서비스를 기준으로 사용 비중을 본다면 Redux가 아직은 절대다수를 차지하고 있습니다. 즉, 현존하는 서비스를 유지 보수하게 된다면 높은 확률로 Redux겠죠.

상태 관리를 위해 무엇을 선택할지에 대해서는 많은 사람들이 프로젝트 규모가 크다면 Redux, 그렇지 않다면 Context API 정도만 사용해도 충분할 거라고 말합니다. (이 외에도 비동기 등을 다루거나 하면 리덕스를 사용하는 것이 좋다고 언급되는데 직접 해본 부분이 아니라서 말을 아끼겠습니다) 그리고 최근 많이 언급되는 것이 올해 중순에 따끈따끈하게 등장한 Recoil입니다.

Recoil 맛보기

 

Recoil

A state management library for React.

recoiljs.org

Recoil은 스스로도 minimal 한 점을 내세우고 있습니다. presentational component를 만들고 리듀서와 모듈을 만들어서 또 불러오는 긴 작업이 필요한 Redux와 다르게, 리액트의 useState hook처럼 작성되기 때문에 저처럼 상태 관리에 낯설고 Redux 잘 모르는 사람 입장에서는 입문하기엔 좋다는 생각이 듭니다. (물론, 상태 관리가 왜 어렵고 어떤 흐름을 가져야 하는지를 배울 수 있을지는 잘 모르겠습니다.)

공식 docs와 함께 참고한 영상인데, 해당 영상에서 사용한 코드는 이렇습니다.

import logo from "./logo.svg";
import "./App.css";
import { useState } from "react";
import {
  atom,
  RecoilRoot,
  selector,
  useRecoilState,
  useRecoilValue,
} from "recoil";

const usernameState = atom({
  key: "username",
  default: "Red",
});

const countState = selector({
  key: "count",
  get: ({ get }) => {
    const username = get(usernameState);
    return username.length;
  },
});

function Nav() {
  const username = useRecoilValue(usernameState);

  return (
    <div className="nav">
      <p>{username}</p>
    </div>
  );
}

function Body() {
  return (
    <div className="body">
      <Profile />
      <Count />
    </div>
  );
}

function Profile() {
  const [username, setUsername] = useRecoilState(usernameState);
  return (
    <div>
      <h2>Profile:</h2>
      <o>{username}</o>
      <input
        type="text"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
      />
    </div>
  );
}

function Count() {
  const count = useRecoilValue(countState);

  return (
    <div className="Count">
      <p>Count: {count}</p>
    </div>
  );
}

function App() {
  return (
    <RecoilRoot>
      <Nav />
      <Body />
    </RecoilRoot>
  );
}

export default App;

atom

Recoil이 비교적 간단하다고 느껴지는 건, 관리할 상태를 atom을 이용해서 간단하게 세팅해두고 바로 필요한 컴포넌트에서 불러올 수 있기 때문인 거 같습니다. recoil로 상태 관리가 필요한 컴포넌트만 제일 상단에서 RecoilRoot 태그로 감싸면 됩니다. 그렇게만 해두면, 컴포넌트 안에서 원하는 상태를 변수에 할당하여 조금 더 직관적으로 사용할 수 있고, 명명도 원하는 형태로 자유롭게 할 수 있습니다. 무엇보다 useState를 사용할 줄 안다면, 해당 부분을 단순히 useRecoilState로만 바꿔주면 됩니다. 시각적으로(?) 봤을 때 리액트적입니다.

selector

공식 문서에서 설명하는 selector는 아래와 같다.

A selector represents a piece of derived state. You can think of derived state as the output of passing state to a pure function that modifies the given state in some way.
selector는 파생된 상태를 나타냅니다. 파생된 상태란, 순수 함수에 어떤 상태를 넘겨 변화를 가한 결과 정도로 이해하면 됩니다.

즉, 어떤 상태에서 우리가 원하는 결과를 얻기 위해 filter를 하거나 연산을 하는 등, 뭔가 변화를 주어야 하는 경우가 있는데, 이때 사용할 수 있습니다. 상세하게 다루기엔 아직 지식이 부족하여, 간단하게 get과 set을 이용하여 값을 읽기 전용으로 반환받거나 수정할 수 있다는 것 정도만 적고 넘어가겠습니다.

마치며

redux, recoil, Context API 모두 대략 튜토리얼을 따라 해 보았는데, 입문자 입장에선 여전히 결론이 없는 것 같습니다. 처음엔 튜토리얼 정도 따라 해 보면 감이 오겠거니... 하고 시작했는데 하고 나니 더욱 혼란이 가중되는 기분입니다. 각각의 장단점이 비교적 명확한 느낌이고, 또 신기술은 사용에 부담이 있는 것도 사실입니다. 대충 얻어진 결론은 결국 '다 해봐야 안다' 정도가 된 것 같은데, 이와 관련해서는 앞으로 개인 프로젝트에서 각각을 적용해보며 느낀 점을 정리해보려고 합니다.