티스토리 뷰

전역 상태값을 관리하기 위해 기존에는 Redux를 사용하고 있었다.

처음 동작 원리에 대해 이해하는것도 매우 힘들었고,

사용하면 할수록 써야할 코드 양도 많고, 매번 비슷한 코드를 여러군데에서 계속 써야하는게 너무 귀찮았다.

이게 진짜 최선일까? 너무 비효율적인데 하는 생각을 하던 와중에 Recoil이라는 것을 발견했다.

 

React에서 상태값을 관리하기 위해서 사용되는 기술은 여러개인데

Redux 같은 상태관리 라이브러리를 사용하는 방법뿐만 아니라

Context API를 사용하는 방법, 라이브러리 없이 직접 state를 사용하는 방법이 있다.

 

직접 state를 사용하는 방법

작은 프로젝트에서는 괜찮겠지만

어느정도 규모가 있는 프로젝트에서는 Prop Drilling이 과도하게 발생할 수 있다.

Prop Drilling이란? 

상위 컴포넌트에서 하위 컴포넌트로 props를 전달하는 과정이다.

prop 전달이 과도하게 이루어진다면 추적이 힘들어지고, 유지보수도 어려워진다.

이때문에 전역 상태관리 라이브러리를 사용한다.

 

Context API를 사용하는 방법

익혀야 하는 개념과 작성해야하는 코드 양이 그렇게 적지 않다.

Redux와 비교했을 때 비슷한 정도라서 굳이 Context API로 갈아타지 않는다.

 

상태관리 라이브러리를 사용하는 방법

Recoil은 페이스북에서 만든 기술로, 오직 React만을 위해 만들었기 때문에 가장 React스러운 상태관리 라이브러리라고 볼 수 있다.

 

React에서의 데이터 흐름은 단방향으로 흐른다.

즉, 위에서 아래로 = 상위에서 하위로 = 부모에서 자식으로 흐른다.

Flux 패턴에 의해 적용된 방식이다.

Flux 패턴은 MVC 패턴으로 부터 시작했다.

MVC 패턴

Model View Controller

Model에 데이터를 정의해놓고, Controller를 통해 Model의 데이터를 CRUD하며, 이를 View에 띄워준다.

프로젝트 규모가 커지면 정의된 Model과 이를 출력하는 View가 다양해진다.

데이터 흐름이 많아진다.

어떤 데이터가 변경되면 해당 데이터를 사용하는 곳 전부에서 코드 수정이 필요하다.

이 과정에서 예상치 못한 오류 발생이나 예상과 다른 방식으로 동작하기도 한다.

이를 해결하기 위해 Flux 패턴이 탄생했다.

Flux 패턴

Action Dispatcher Model View 

Dispatcher는 Action을 감지하여 Store(Model)에 Action을 전달해준다.

Model에 저장된 값을 가져와 View에 뿌려주며, View는 가져온 데이터들을 화면에 렌더링해준다.

 

Redux

Flux 패턴을 적용한 상태관리 라이브러리이다.

Store에 상태값을 저장해놓고, 상태에 변화가 필요할 때 Action을 Dispatch하여 Reducer를 통해 상태를 변경한다.

Redux를 사용하기 위해 Store, Action, Dispatcher, Reducer 등 구현해야 할 코드가 방대하다.

여러 작업자들이 함께 작업하는 경우 잘 정해진 규칙이 없다면 코드가 매우 복잡해진다.

(보일러 플레이트란? 최소한의 변경으로 여러곳에서 재사용되며, 반복적으로 비슷한 형태를 띄는 코드)

이에 비해 Recoil로 상태를 관리하는 방법은 간단하다.

 

또한 Redux에서 비동기 로직을 사용하려면 미들웨어를 사용해야만 하는데

(다음에 알아볼 redux-thunk, redux-saga와 같은)

Recoil에서는 내장된 selector를 통해 미들웨어 없이 비동기 로직 처리가 가능하다.

 

Recoil

atom

상태를 담는 곳으로

key를 통해 구분하고, default를 통해 초기값을 지정해줄 수 있다.

import { atom } from "recoil"

export const user = atom({
  key: "user",
  default: {
    id: "root",
    pwd: "root",
  },
});

가져다 쓸 때에는 useState와 비슷하게 사용된다.

import { useRecoilState } from "recoil";
import { user } from "./store";

export function Test() {
  const [user, setUser] = useRecoilState(user);
  
  return (
    <div>
      <span>{user.id}</span>
    </div>
  );

}

atom과 atom의 modifier를 분리해서도 쓸 수 있다.

즉 getter, setter를 분리해서 사용할수도 있다.

import { useSetRecoilState, useRecoilValue } from "recoil";
import { user } from "./store";

export function Example() {
  const setUser = useSetRecoilState(user);
  const user = useRecoilValue(user);

  return (
    <div>
      <span>{user.id}</span>
    </div>
  );

}

 

selector

atom을 활용해 개발자가 원하는 대로 값을 뽑아 사용할 수 있는 API

function selector<T>({
  key: string,

  get: ({
    get: GetRecoilValue,
    getCallback: GetCallback,
  }) => T | Promise<T> | RecoilValue<T>,

  set?: (
    {
      get: GetRecoilValue,
      set: SetRecoilState,
      reset: ResetRecoilState,
    },
    newValue: T | DefaultValue,
  ) => void,
}
)

값을 조회하는 예시 코드를 보자면

import { atom, selector } from "recoil";

export type status = "DONE" | "DOING";

interface toDo {
  status: status;
  contents: string;
}

export const selectStatus = atom<status>({
  key: "nowStatus",
  default: "DOING"
});

export const toDos = atom<toDo[]>({
  key: "toDos",
  default: [
    { status: "DOING", contents: "default 1" },
    { status: "DONE", contents: "default 2" },
    { status: "DONE", contents: "default 3" },
    { status: "DOING", contents: "default 4" },
    { status: "DOING", contents: "default 5" }
  ]
});

export const selectToDo = selector<toDo[]>({
  key: "selectToDos",
  get: ({ get }) => {
    const originalToDos = get(toDos);
    const nowStatus = get(selectStatus);
    return originalToDos.filter((toDo) => toDo.status === nowStatus);
  }

});

get이라는 내장 함수를 통해 저장되어 있는 atom이나 다른 selector값을 받아올 수 있다.

 

값을 변경하는 예시 코드를 보자면

export const selectToDo = selector<toDo[]>({
  key: "selectToDos",
  get: ({ get }) => {
    const originalToDos = get(toDos);
    const nowStatus = get(selectStatus);
    return originalToDos.filter((toDo) => toDo.status === nowStatus);
  },
  set: ({ set }, newToDo) => {
    set(toDos, newToDo);
  }
}
);

set이라는 메소드를 통해 첫번째 인자로는 변경할 atom 값, 두번째 인자로는 변경해줄 값을 넣는다.

 

이들을 호출할때에는 이렇게 활용해주면 된다.

import React from "react";
import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
import { selectStatus, selectToDo } from "./atom";

export default function App() {
    const selectToDos = useRecoilValue(selectToDo);
    const setNewToDos = useSetRecoilState(selectToDo);
    ...
    setNewToDos(새로 변경할 값);
    ...
}

Recoil에서 비동기 로직을 사용하기 위해서는 selector를 사용하면 되는데

네트워크 통신을 통해 값을 비동기적으로 받아와야 하는 경우

export const selectId = atom({
  key: "selectId",
  default: 1
});

export const selectingUser = selector({
  key: "selectingUser",
  get: async ({ get }) => {
    const id = get(selectId);
    const user = await fetch(
      `https://jsonplaceholder.typicode.com/users/${id}`
    ).then((res) => res.json());
    return user;
  },
  set: ({ set }, newValue) => {
    set(nowUser as any, newValue);
  }
});
export const selectUser = selectorFamily({
  key: "selectOne",
  get: (id: number) => async () => {
    const user = fetch(
      `https://jsonplaceholder.typicode.com/users/${id}`
    ).then((res) => res.json());
    return user;
  }
});

// 컴포넌트에서 사용 시 

const user = useRecoilValue<IUser>(selectUser(id));

이렇게 사용할 수 있다.

 

그리고 내부적으로 캐싱을 해줘서 비동기 통신으로 값이 캐싱되어 있다면 매번 통신하지 않고 캐싱된 값을 추적해준다.

 

 

끗 ㅡ

이렇게 편리한 Recoil로 지금 사용하고 있는 회사 템플릿 다 변경해버리고 싶다.

다음주에 Recoil 적용한 템플릿으로 제작해봐야겠다.

 

 

출처 : https://tech.osci.kr/2022/06/16/recoil-state-management-of-react/

 

Recoil, 리액트의 상태관리 라이브러리 - 오픈소스컨설팅 테크블로그

Recoil 만을 위한 글이지만, 해당 기술을 탐구하기 전에 같은 문제를 해결하기 위해 사용되고 있는 라이브러리와 비교를 하는것은 상당히 중요한 일이라고 생각합니다.Frontend 개발을 하면서 state

tech.osci.kr

정말 도움 많이 된 블로그,,,,, 감사합니다,,,🙏

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함