Recoil - 사용법

Seuling·2022년 7월 7일
0

FE

목록 보기
33/42
post-thumbnail

Recoil

  • 작고 React스러운
    : 빠르고 유연한 공유되는 상태

  • 데이터 흐름 그래프
    : 파생 데이터와 비동기 쿼리는 순수 함수와 효율적인 구독으로 관리됨

  • 교차하는 앱 관찰
    : 코드 분할을 손상시키지 않고 앱 전체의 모든 상태 변경을 관찰하여 지속성, 라우팅, 시간 이동 디버깅 또는 실행 취소를 구현함

  • 페이스북에서 만듦!

왜 나왔지 ?

호환성 및 단순함을 이유로 외부의 글로벌 상태관리 라이브러리보다는 React 자체에 내장된 상태 관리 기능을 사용하는 것이 가장 좋다. 그러나 React는 다음과 같은 한계가 있다.

  • 컴포넌트의 상태는 공통된 상위요소까지 끌어올려야만 공유될 수 있으며, 이 과정에서 거대한 트리가 다시 렌더링되는 효과를 야기하기도 한다.
  • Context는 단일 값만 저장할 수 있으며, 자체 소비자(consumer)를 가지는 여러 값들의 집합을 담을 수는 없다.
  • 이 두가지 특성이 트리의 최상단(state가 존재하는 곳)부터 트리의 말단(state가 사용되는 곳)까지의 코드 분할을 어렵게한다.

Recoil은 직교(orthogonal)하지만 본질적인 방향 그래프를 정의하고 React 트리에 붙인다. 상태 변화는 이 그래프의 뿌리(atoms)로부터 순수함수(selectors)를 거쳐 컴포넌트로 흐르며, 다음과 같은 접근 방식을 따른다.

주요 개념

Atoms

Recoil을 사용하면 atoms (공유 상태)에서 selectors (순수 함수)를 거쳐 React 컴포넌트로 내려가는 data-flow graph를 만들 수 있다. Atoms는 컴포넌트가 구독할 수 있는 상태의 단위다. Selectors는 atoms 상태값을 동기 또는 비동기 방식을 통해 변환한다.

시작하기 - 주요개념

1. 설치
npm install recoil
2. APP.Js에 RecoilRoot 연결
import { RecoilRoot } from "recoil";

function App() {
  return (
    <div className="App">
      <RecoilRoot>
      </RecoilRoot>
    
    </div>
  );
}

export default App;
3. Atom

src > components > store.js

import { atom } from "recoil";

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

src > components > FontButton.jsx

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

export default function FontButton() {
    const [fontSize, setFontSize] = useRecoilState(fontSizeState); 
  //앞에서 atom으로 감싸준 뿌리 root 상태 값 넣어줌 (fontSizeState)
    return (
      <button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}>
        Click to Enlarge
      </button>
    );
  }

src > components > Text.jsx

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

export function Text() {
    const [fontSize] = useRecoilState(fontSizeState);
  //font의 값을 꺼내오고,
    return <p style={{fontSize}}>This text will increase in size too.</p>;
  //p의 스타일로 폰트에 넣어줌
  }

App.js

import { RecoilRoot } from "recoil";
import FontButton from "./components/RecoilExample/FontButton";
import Text from "./components/RecoilExample/Text";

function App() {
  return (
    <div className="App">
      <RecoilRoot>
        <FontButton />
        <Text />
      </RecoilRoot>
    </div>
  );
}

export default App;

버튼에도 fontsize가 들어있고, text에도 fontsize가 들어있다. 두가지 컴포넌트는 서로 다르고, provider로 감싸있는 거지만, 굉장히 간단한 atom을 하나 추가해 놓고 useState와 유사한 useRecoilState라는 훅으로 감싸서 간단하게 처리함
atom : 기본 state를 주고, 다른곳에서 쓸 때는 useRecoilState 사용

Selectors

Selector는 atoms나 다른 selectors를 입력으로 받아들이는 순수 함수(pure function)다. 상위의 atoms 또는 selectors가 업데이트되면 하위의 selector 함수도 다시 실행된다. 컴포넌트들은 selectors를 atoms처럼 구독할 수 있으며 selectors가 변경되면 컴포넌트들도 다시 렌더링된다.

src > components > store.js

import { atom, selector } from "recoil";

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

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

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

src > components > FontButton.jsx

import { useRecoilState, useRecoilValue } from "recoil";
import { fontSizeLabelState, fontSizeState } from "./store";

export default function FontButton() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  const fontSizeLabel = useRecoilValue(fontSizeLabelState);

  return (
    <>
      {" "}
      <div>Current font size: {fontSizeLabel}</div>
      <button
        onClick={() => setFontSize((size) => size + 1)}
        style={{ fontSize }}
      >
        Click to Enlarge
      </button>
    </>
  );
}

atom이나 다른 셀렉터의 값을 보며 값이 바뀌면 자기도 바뀜
값이 바뀌는데 무엇인가 더하거나(여기예제에선 px라는 unit을 더해줌), 값을 변경을 취해서 보여주는 것
selector는 주로 값을 꺼내서쓴다!

연습예제

src > components > CounterStore.js

import { atom } from "recoil";

export const textState = atom({
  key: "textState", // unique ID (with respect to other atoms/selectors)
  default: "", // default value (aka initial value)
});

src > components > CharacterCounter.jsx

import { useRecoilState, useRecoilValue } from "recoil";
import { charCountState, textState } from "./CounterStore";

export default function CharacterCounter() {
  return (
    <div>
      <TextInput />
      <CharacterCount />
    </div>
  );
}

function TextInput() {
  const [text, setText] = useRecoilState(textState);

  const onChange = (event) => {
    setText(event.target.value);
  };

  return (
    <div>
      <input type="text" value={text} onChange={onChange} />
      <br />
      Echo: {text}
    </div>
  );
}

function CharacterCount() {
  const count = useRecoilValue(charCountState);

  return <>Character Count: {count}</>;
}

src > components > FontButton.jsx

import { useRecoilState, useRecoilValue } from "recoil";
import { textState } from "./CounterStore"; //추가
import { fontSizeLabelState, fontSizeState } from "./store";

export default function FontButton() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  const fontSizeLabel = useRecoilValue(fontSizeLabelState);
  const [text] = useRecoilState(textState); //추가

  return (
    <>
      {" "}
      <div>Current font size: {fontSizeLabel}</div>
      <button
        onClick={() => setFontSize((size) => size + 1)}
        style={{ fontSize }}
      >
        Click to Enlarge {text} //추가
      </button>
    </>
  );
}

리코일 정리

React의 한계 해소

 -상태 공유를 위해 상위 요소까지 끌어올리면 거대한 트리가 재렌더 될 가능성 있음
 - Context는 단일 값만 저장할 수 있고, 자체 Consumer를 가지는 여러값의 집합은 담을 수 없음
 - 최상단(state가 존재하는 곳)부터 트리의 잎(state가 사용되는 곳)까지의 코드 분할이 어려움

atoms/ selectors

atoms(상태) : 고유한 키를 가짐
selectors(순수함수): atoms이나 selectors를 입력으로 받음
components

React Tree

Tree를 벗어난 3D shared state

profile
프론트엔드 개발자 항상 뭘 하고있는 슬링

0개의 댓글