useReducer나 Redux가 싫다는게 아냐. Recoil이 더 나을거 같단 얘기지(초간단이라고 해놓고 초간단 아님 안 초간단임 어려움)

const job = '프론트엔드';·2023년 8월 28일
0
post-thumbnail

Recoil

페이스북이 개발한 전역상태관리 라이브러리

Recoil을 공부하게 된 계기

  1. Redux 싹 이해했더니, 그럼 Recoil은 더 쉽다고 하길래
  2. 진짜 뵹아리때 면접 생각나서
    상태가 뭔지도 모르고, 상태관리는 더더욱 뭔지 몰랐을 때 제일 처음 팀플에서 Recoil로 상태관리를 했던 적이 있음. 그때는 그냥 사람들이 해야 된다길래 함. 면접에서 질문을 받음

🧔🏻‍♂️면접관: Recoil로 상태관리를 했는데, 어떤 방식으로 했죠??
👧🏻나: 제가 관리 안했는데여?...

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

근데, 그거알죠? React를 개발한 곳 = facebook

React를 만든 곳에서 React 상태관리를 편리하게 도와주려고 개발한 상태관리 라이브러리가 있다?!⭐ 외않씀??

리액트 상태관리의 문제점

  1. 컴포넌트의 상태는 공통된 상위요소까지 끌어올려야 공유될 수 있음
    거대한 트리가 리렌더 되어야 함
  2. Context는 단일 값만 저장할 수 있으며 여러 값의 집합을 담을 수 없음 - 계속 provider로 감싸줘야 했음

트리의 최상단(선언하는 곳)부터 트리의 말단(사용하는 곳) 까지의 코드 분할이 어려워짐

Recoil(리코일) 구조 및 흐름?

상태변화는 트리의 뿌리(atoms)로 부터 순수 함수(selectors)를 거쳐 컴포넌트로 흐름

Recoil 설치 방법

npm i recoil

of

yarn add recoil

✅ Atoms

  • 상태의 단위
  • 업데이트와 구독이 가능
  • atom이 업데이트 하면, 해당 atom을 구독하고 있는 다른 컴포넌트는 새로운 값을 반영(re-render)
  • atoms는 런타임에서 생성가능
  • 리액트의 로컬 컴포넌트 상태 대신 사용가능
  • 동일한 atom이 여러 컴포넌트에서 사용되는 경우 모든 컴포넌트 상태를 공유
const fontSizeState = atom({
	key: 'fontSizeState',
    default: 14,
 });
  • API에 사용되는 '고유한' 'key'가 필요 / 기본값도 갖음
  • 같은 키를 갖는 것은 오류, 전역적으로 고유한 값이어야 함

✅ useRecoilState

  • atom을 읽고 쓰기 위해서는 useRecoilState 훅 필요

간단한 구조로 실습 예제)

atom함수 만들기

import { atom } from "recoil";

const fontSizeState = atom({
  key: "fontSizeState",
  default: 14,
});

export default fontSizeState;

font크기가 증가하는 버튼 만들기

import React from "react";
import { useRecoilState } from "recoil";
import fontSizeState from "../store/fontSizeState";

const FontButton = () => {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);

  return (
    <button
      onClick={() => setFontSize((size) => size + 1)}
      style={{ fontSize }}
    >
      커지는 버튼
    </button>
  );
};

export default FontButton;

font크기가 증가하는 text 만들기

import React from "react";
import { useRecoilState } from "recoil";
import fontSizeState from "../store/fontSizeState";

const Text = () => {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  return <p style={{ fontSize }}> 커진닷 </p>;
};

export default Text;

의문점.. '어? 각각의 컴포넌트인데 state뭔가 연결고리가 없어보이는데, 컴포넌트가 분리된 상태에서도 font사이즈에 대한 state 공유가 가능하다고?'

그래서 직접 코드를 작성해서 실행시킴

  • 그러니깐, FontButton 컴포넌트에 만들어둔 업데이트 상태가 useRecoilState(fontSizeState)로 fontSizeState를 쓰고자 하는 모든 컴포넌트에 공유
  • 그래서 Text 컴포넌트에서 상태를 공유받을 수 있었던 것

✅ Selectors

  • atoms나 다른 selectors를 입력으로 받아들이는 수수 함수
  • 상위의 atoms 또는 selector가 업데이트 되면 하위의 selector 함수도 다시 실행
  • 컴포넌트들은 selectors를 atoms처럼 쓸 수 있고, selectors가 변경되면 컴포넌트들도 리렌더 됨
import { selector } from "recoil";
import fontSizeState from "./fontSizeState";

const fontSizeLabelState = selector({
  key: "fontSizeLabelState",
  get: ({ get }) => {
    const fontSize = get(fontSizeState);
    const unit = "px";

    return `${fontSize}${unit}`;
  },
});

export default fontSizeLabelState;

get 속성

  • 계산될 함수
  • 전달되는 get 인자를 통해 atoms, 다른 selector의 접근이 가능(자동으로 종속관계가 생성)
  • 따라서 참조했던 다른 atoms, selectors가 업데이트 되면 리렌더 됨
  • 그러니깐 앞서 atom에 만들어둔 fontSizeState를 인자로 받아 값을 반환함 (= selector로 fontSizeState에 접근하는 것임)

✅ useRecoilValue

  • selectors는 useRecoilValue()를 사용해 읽을 수 있음
  • useRecoilValue()는 하나의 atom이나 selector를 인자로 받아 대응하는 값을 반환(그래서 selector안에 return이 있음)함
  • 따라서, 항목을 '읽기 위한 것으로' 사용

✅ useSetRecoilState

  • setValue에 대한 상태를 여기에 담아둠
    cf) 그럼 다른 곳에서도 useSetRecoilState를 사용해서 setValue의 상태를 담을 수 있을까? 그렇다

❓❓❓❓❓왜? 어째서? 이 부분이 의문이었던 이유

const [state, setState] = useState();

일반적으로 이렇게 쓰니까 당연히 '왜 setState를 다른 곳에서 또 쓴다고???? 으엥???? 그럼 다른 곳에서 쓰는 setState로 상태를 업데이트를 하면 헷갈리지 않을까? 그러면 setState 둘 중 뭐를 써야하지?' 이런 쓸모없는 고민을 했음. 그리고 나는 recoil을 쓰지 않겠다고 다짐함. 하지만 나는 바보였음.

🔔어디에서 setState로 상태를 업데이트를 setState값은 바로 현재 state가 되기 때문에 상관이 없었음

그러니깐,

setValue는 바로 최신의 state로 업데이트 되기 때문 여러곳에서 setValue 함수를 만들어놔도 그 상태 자체가 바로 업데이트 되기 때문에 다른 곳에 만들어진 useSetRecoilState는 서로에게 영향을 미치지 않음

const [fontSize, setFontSize] = useRecoilState(fontSizeState);

이렇게 만들어진 것도, 다른 컴포넌트에서

const setFontSize = useSetRecoilState(fontSizeState) 

이렇게 사용될 수 있음

비동기 데이터 쿼리

  • Recoil에서는 동기/비동기 함수 모두 selector에서 처리 가능
  • Promise를 리턴하거나 혹은 async 함수를 사용
  • 의존성에 하나라도 변경점이 생긴다면, selector는 재실행
const currentUserIDState = atom({
  key: 'CurrentUserID',
  default: 1,
});

const currentUserNameQuery = selector({
  key: 'CurrentUserName',
  get: async ({get}) => {
    const response = await myDBQuery({
      userID: get(currentUserIDState),
    });
    return response.name;
  },
});

function CurrentUserInfo() {
  const userName = useRecoilValue(currentUserNameQuery);
  return <div>{userName}</div>;
}
  • Recoil은 보류중인 데이터를 다루기 위해 React Suspense와 함께 동작하도록 디자인
  • 컴포넌트를 Suspense의 경계로 감싸는 것으로 아직 보류중인 하위 항목들을 잡아내고 대체하기 위한 UI를 렌더

더미데이터 API 사용

RecoilRoot로 index.tsx 감싸주기

import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";

import { RecoilRoot } from "recoil";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <RecoilRoot>
    <App />
  </RecoilRoot>
);

Id 상태관리(atom)

import { atom } from "recoil";

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

export default todoIdState;
  • key값은 todoIdState로 하고, default값은 1로 함

비동기로 상태관리(selector + axios)

import { selector } from "recoil";
import todoIdState from "./todoIdState";
import axios from "axios";

const todoItemQuery = selector({
  key: "todoItemQuery",
  get: async ({ get }) => {
    const id = get(todoIdState);

    const response = await axios.get(
      `https://jsonplaceholder.typicode.com/todos/${id}`
    );

    return response.data;
  },
});

export default todoItemQuery;
  • key값은 todoItemQuery이고
  • get으로 비동기처리 todoIsState를 get으로 받아와서 id값에 넣고
  • api를 await으로 받아와서 response에 넣음
  • 이 todoItemQuery의 반환값은 response.data가 됨
  • 따라서 todoItemQuery를 가져다 쓰는 곳은 response.data의 값을 상태로 사용

데이터 확인하기

import React from "react";
import { useRecoilValue } from "recoil";
import todoItemQuery from "./store/todoItemQuery";

function App() {
  const data = useRecoilValue(todoItemQuery);
  console.log(data);
  
  return <div>{data.title}</div>;
}

export default App;

  • 잘 받아옴
  • 경고(Warning)는 Suspense 처리를 해주지 않아서 그러함

이게 리코일 구조의 전부라고 볼 수 있음(공식문서에 따르면)

🖥 atom에 최신상태를 만들어주거나 selector로 값을 받아와서 반환값을 만들어주는 기능도 가능하고, 원하는 상태만을 가져다 쓰거나 업데이트만 시켜주는 것도 가능함 ! 익숙해지면 편리할 것들

느낀점. 리덕스와 리코일을 둘 다 공부했을때, 뭐가 더 낫다 좋다 이것보다 이 부분은 리코일이 편하고 이 부분은 리덕스가 유리하겠다 ! 라는 느낌이 있었음.

자그마한 리코일 실습을 해보면 더 이해가 될 거 같아서 다음엔 쪼그만 실습을 해볼 예정!

profile
`나는 ${job} 개발자`

0개의 댓글