Recoil

Gavri·2021년 10월 15일

리코일 뿌셔~👊

Recoil 라이브러리를 만들게 된 동기 Feat.Docs

Docs 내용 자체를 퍼온거기에 간단하게 읽으시면 될 것 같습니다.

호환성과 단숨함의 이유로 외부의 글로벌 상태관리 라이브러리보다 React 자체에 내장된 상태 관리 기능을 사용하는 것이 가장 좋다.

하지만 리액트는 이런 문제가 있다

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

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

  • 우리는 공유상태(shared state)도 React의 내부상태(local state)처럼 간단한 get/set 인터페이스로 사용할 수 있도록 boilerplate-free API를 제공한다. (필요한 경우 reducers 등으로 캡슐화할 수도 있다.)
  • 우리는 동시성 모드(Concurrent Mode)를 비롯한 다른 새로운 React의 기능들과의 호환 가능성도 갖는다.
  • 상태 정의는 증분 및 분산되므로 코드 분할이 가능하다.
  • 상태를 사용하는 컴포넌트를 수정하지 않고도 상태를 파생된 데이터로 대체할 수 있다.
  • 파생된 데이터를 사용하는 컴포넌트를 수정하지 않고도 파생된 데이터는 동기식과 비동기식 간에 이동할 수 있다.
  • 우리는 탐색을 일급 개념으로 취급할 수 있고 심지어 링크에서 상태 전환을 인코딩할 수도 있다.
    역호환성 방식으로 전체 애플리케이션 상태를 유지하는 것은 쉬우므로, 유지된 상태는 애플리케이션 변경에도 살아남을 수 있다.

주요 개념

Atoms

  • Atoms은 상태의 단위 이며, 업데이트와 구독이 가능하다.
  • atom이 업데이트되면 각각의 구독된 컴포넌트는 새로운 값을 반영하여 다시 렌더링 된다.
  • atoms는 런타임에서 생성될 수도 있다.
  • Atoms는 React의 로컬 컴포넌트의 상태 대신 사용할 수 있다.
  • 동일한 atom이 여러 컴포넌트에서 사용되는 경우 모든 컴포넌트는 상태를 공유한다.
atom 함수를 이용하여 생성
const fontSizeState = atom({
  key: 'fontSizeState',
  default: 14,
});

Atoms는 디버깅, 지속성 및 모든 atoms의 map을 볼 수 있는 특정 고급 API에 사용되는 고유한 키가 필요하다. 두개의 atom이 같은 키를 갖는 것은 오류이기 때문에 키값은 전역적으로 고유하도록 해야한다. React 컴포넌트의 상태처럼 기본값도 가진다.

컴포넌트에서 atom을 읽고 쓰려면 useRecoilState라는 훅을 사용한다. React의 useState와 비슷하지만 상태가 컴포넌트 간에 공유될 수 있다는 차이가 있다.

function FontButton() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  return (
    <button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}>
      Click to Enlarge
    </button>
  );
}

버튼을 클릭하면 버튼의 글꼴 크기가 1만큼 증가하며, fontSizeState atom을 사용하는 다른 컴포넌트의 글꼴 크기도 같이 변화한다.

selectors

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

순수 함수란? - 어떤 함수에 동일한 인자를 주었을 때 항상 같은 값을 리턴하는 함수
function add(a,b) { return a + b } // 순수함수
let c = 10;
function add(a,b,c) { return a + b + c } // !== 순수함수

Selectors는 상태를 기반으로 하는 파생 데이터를 계산하는 데 사용된다. 최소한의 상태 집합만 atoms에 저장하고 다른 모든 파생되는 데이터는 selectors에 명시한 함수를 통해 효율적으로 계산함으로써 쓸모없는 상태의 보존을 방지한다.

Selectors는 어떤 컴포넌트가 자신을 필요로하는지, 또 자신은 어떤 상태에 의존하는지를 추적하기 때문에 이러한 함수적인 접근방식을 매우 효율적으로 만든다.

컴포넌트의 관점에서 보면 selectors와 atoms는 동일한 인터페이스를 가지므로 서로 대체할 수 있다.

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

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

get 속성은 계산될 함수이다. 전달된 get 인자를 통하여 atoms와 다른 selectors에 접근할 수 있다. 접근 하면 자동으로 종속관계가 되며 atoms와 selectors 가 업데이트 되면 이 함수도 재실행된다.

이 fontSizeLabelState 예시에서 selector는 fontSizeState라는 하나의 atom에 의존성을 갖는다.
개념적으로 fontSizeLabelState selector는 fontSizeState를 입력으로 사용하고 형식화된 글꼴 크기 레이블을
출력으로 반환하는 순수 함수처럼 동작한다.

Selectors는 useRecoilValue()를 사용해 읽을 수 있다.
useRecoilValue()는 하나의 atom이나 selector를 인자로 받아 대응하는 값을 반환한다.
fontSizeLabelState selector는 writable(쓰기)하지 않기 때문에 useRecoilState()를 이용하지 않는다.

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

  return (
    <>
      <div>Current font size: ${fontSizeLabel}</div>

      <button onClick={setFontSize(fontSize + 1)} style={{fontSize}}>
        Click to Enlarge
      </button>
    </>
  );
}

길고긴 설명이 끝낫다 설치 시작🤤

npx create-react-app my-app
yarn add recoil

설치끝!

만들어 볼것


인풋을 통해 값을 전달 받고 그값을 atom 에 저장하고 저장된 값을 바탕으로 selector 를 사용하여 text.length를 보여주는 앱을 만들어보자.

RecoilRoot

먼저 recoil 상태를 사용하는 컴포넌트는 부모 트리 어딘가에 나타나는 RecoilRoot가 필요하다
루트 컴포넌트가 RecoilRoot를 넣기에 가장 좋은 장소이다.(App.js)

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

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

atom

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

Atom은 상태(state)의 일부를 나타낸다. Atoms는 어떤 컴포넌트에서나 읽고 쓸 수 있다.
atom의 값을 읽는 컴포넌트들은 암묵적으로 atom을 구독한다.
그래서 atom에 어떤 변화가 있으면 그 atom을 구독하는 모든 컴포넌트들이 재 렌더링 되는 결과가 발생할 것이다.

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

function TextInput() {
  const [text, setText] = useRecoilState(textState); // atom 사용법 useRecoilState(key)

  const onChange = (event) => {
    setText(event.target.value);//setText로 지정한 atom의 값을 변경
  };

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

Selector

Selector는 파생된 상태(derived state)의 일부를 나타낸다. 파생된 상태는 상태의 변화다. 파생된 상태를 어떤 방법으로든 주어진 상태를 수정하는 순수 함수에 전달된 상태의 결과물로 생각할 수 있다.

const charCountState = selector({
  key: 'charCountState', // unique ID (with respect to other atoms/selectors)
  get: ({get}) => { 
    const text = get(textState);// get(atomKey) 를 통해 값을 전달 받는다. atom의 값이 바뀌면 함수도 재실행

    return text.length;
  },
});

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

  return <>Character Count: {count}</>;
}
profile
모든건 기록으로

0개의 댓글