개인 프로젝트에서 Recoil을 이용해서 만들어야 하는 미션이 있다.
Recoil 이라는 개념이 생소하기 때문에 정리하면서 개념을 익혀보았고 프리온보딩 강의 중 Recoil에 관련된 내용도 추가적으로 정리했다.

1. Recoil 을 사용하는 이유

리액트의 한계

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

Recoil의 방향성

Recoil 은 본질적인 방향 그래프를 정의하고 React 트리에 붙인다. 상태 변화는 이 그래프의 뿌리(atoms) 로 부터 순수함수(selectors)를 거쳐 컴포넌트로 흐른다.

2. 주요 개념

Recoil을 사용하면 atoms(공유 상태)에서 selectors(순수 함수)를 거쳐 React 컴포넌트로 내려가는 data-flow graph 를 만들 수 있다.

Atoms

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

Atoms 는 atom 함수를 사용해 생성한다.

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

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

useRecoilState

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

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

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

function Text() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  return <p style={{fontSize}}>This text will increase in size too.</p>;
}

Selectors

Selector 는 atoms나 다른 selector를 입력으로 받아들이는 순수함수 이다.

순수함수 : 부수효과가 없는 함수, 어떤 함수에 동일한 인자를 주었을 때 항상 같은 값을 리턴하는 함수, 외부 상태를 변경하지 않는 함수

상위의 atoms 또는 selectors 가 업데이트 되면 하위의 selectors 함수도 다시 실행된다. 컴포넌트들은 selectors를 atoms 처럼 구독할 수있고 selectors 가 변경되면 컴포넌트들도 다시 렌더링 된다.

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

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

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

Selectors 는 selector 함수를 통해 정의한다.

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

get 속성은 계산될 함수다. 전달되는 get 인자를 통해서 atoms와 다른 selectors에 접근할 수 있다.
다른 atoms 나 selector 에 접근하면 자동으로 종속 관계가 생성되므로 참조했던 다른 atom 이나 selectors 가 업데이트 되면 이 함수도 다시 실행된다.

이 fontSizeLabelState 예시의 selector 는 fontSizeState 라는 하나의 atom에 의존성을 갖는다. 이 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>
    </>
  );
}

버튼를 클릭하면 버튼의 글꼴 크기가 증가하는 동시에 현재 글꼴 크기를 반영하도록 글꼴 크기 레이블을 업데이트하는 두 가지 작업이 수행된다.

3. 리코일 시작하기

리코일은 React를 위한 상태 관리 라이브러리 이므로 React 가 설치되어있어야 한다.

npx create-react-app 앱이름

설치

#npm

npm install recoil

#yarn

yarn add rec

RecoilRoot

recoil 의 상태를 사용하는 컴포넌트는 부모 트리 어딘가에 RecoilRoot 가 필요하다. 루트 컴포넌트가 RecoilRoot 를 넣기에 가장 적합한 장소이다.

import React from 'react';
import {
  RecoilRoot,
  atom,
  selector,
  useRecoilState,
  useRecoilValue,
} from 'recoil';

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

CharacterCounter 컴포넌트를 구현해 보자.

Atom

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

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

컴포넌트가 atom을 읽고 쓰게 하기 위해서는 useRecoilState()를 아래와 같이 사용한다.

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>
  );
}

Selector

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

const charCountState = selector({
  key: 'charCountState', // unique ID (with respect to other atoms/selectors)
  get: ({get}) => {
    const text = get(textState);

    return text.length;
  },
});

우리는 useRecoilValue() 훅을 사용해서 charCountState 값을 읽을 수 있다.

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

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

프론트앤드 온보딩 강의 中 내용

  • 리코일은 전역으로 쓰는 것이 좋지 않고 한정된 공간 안에서 작게 쓰는 것이 좋다.
  • states 폴더 안쪽에 작성해도 된다.
  • 리코일을 사실 아직 알파버전이여서 규칙도 계속 바뀔 수 있고 리덕스보다 덜 안정적이고 잘못쓰면 코드가 가독성이 떨어지는 경우가 있으므로 atom 함수만 쓰는것을 권장한다.


위와같은 경우를

아래와 같은 식으로 단축해서 쓸수있다.

  • atom 함수는 react 의 useState 처럼 간단하기 때문에 어렵지는 않다.
  • select 나 family 가 붙은 애들은 가급적 안쓰는 것이 좋은데 굉장히 복잡해 질 수 있기 때문이다.
  • atom 만을 이용해서 커스텀 훅을 만들어 그 안에서 결과값을 만들어서 setState 를 하는 것이 좋다.

Reference

https://recoiljs.org/ko/

profile
프론트앤드 개발자로 일하고 있는 kind J 입니다.

0개의 댓글