본문 바로가기

TIL

리액트 hook처럼 생긴 상태 관리 라이브러리 recoil 사용하기

왜 recoil 을 썼는가?

간단하게 다양한 상태를 관리하고 싶었다. 예전 프로젝트에서 props drilling이 일어나는 것을 보면서 상태 관리를 해야 겠다는 필요성을 느꼈지만 내 프로젝트에 빨리 도입 해 볼 수 있는 기술을 찾다가 recoil을 도입하게 되었다.

 

https://recoiljs.org/ko/docs/introduction/getting-started

 

 

Recoil 시작하기 | Recoil

React 애플리케이션 생성하기

recoiljs.org

 

공식 recoil 튜토리얼 페이지에 들어갔을때 너무 놀랐다. 배워야 할 양이 너무 적었기 때문이다. 그리고 모양 조차 함수형 컴포넌트에 익숙한 나에게 hook처럼 보였기 때문에 더 친화적이었다. 

 

그 밖의 이유들

수업 때 들었던 redux는 상대적으로 양이 많았으며 장황한 보일러 플레이트를 만들어야 한다. 이 것이 redux가 성능적으로 부족하다는 얘기는 아니다. 하지만 내 프로젝트의 상태 관리가 비교적 가벼울 것으로 예상되고 빠르게 도입하고 싶었기 때문에 recoil을 선택했다. 또한 recoil은 React의 라이브러리이기 때문에 react처럼 생각하고 접근할 수 있다고 한다. 이 것에 대해선 추후 공부해야겠다. 

 

Atoms

recoil을 사용하기 위해 알아야 하는 가장 중요한 개념인 atom이다.

 

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

-recoil 공식 페이지-

즉 요약하자면, atom은 여러 컴포넌트 들이 구독하는 상태의 단위다. 그리고 selector 함수로 이 atom값을 변환할 수 있다. 

작고 소중한 atoms

 

Recoil 시작하기

 

Atom

Recoil을 시작하기 위해서는 어플리케이션을 RecoilRoot로 감싸고, 데이터를 atom이라는 단위로 선언하여 useState를 Recoil의 useRecoilState로 대체해야 한다. 

이때 루트 컴포넌트에 RecoilRoot를 넣는 것을 공식 페이지에서는 추천한다.  부모 트리로 root를 잡게 되면 하위 자식 요소들은 모두 atom을 구독하는 것이다. 

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

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

이런 식으로 감싸준다. 

 

 

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

 

먼저 atom값을 만들어준다. key와 초깃값이 필요하다. 

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

 

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

완전 useState hook처럼 똑같이 생겼다. 다만 초기값을 직접 넣는 것이 아니라 키값을 넣어주게 된다. 

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

정리하자면 1. atom 만들기 2. hook처럼 사용하기

끝이다. 

 

hook처럼 사용하기

이때 hook처럼 사용할 수 있는 경우는 3가지다. 

흔히 우리가 값과 set함수를 같이 가져올 수도 있고, 값만 가져올 수도 있고, set함수만 가져올 수도 있다. 

 

useRecoilState — atom의 값을 구독하여 업데이트할 수 있는 hook. useState와 동일한 방식으로 사용할 수 있다.

useRecoilValue — setter 함수 없이 atom의 값을 반환만 한다.

useSetRecoilState — setter 함수만 반환한다.

 

import {nameState} from './someplace'
// useRecoilState
const NameInput = () => {
  const [name, setName] = useRecoilState(nameState);
  const onChange = (event) => {
   setName(event.target.value);
  };
  return <>
   <input type="text" value={name} onChange={onChange} />
   <div>Name: {name}</div>
  </>;
}

// useRecoilValue
const SomeOtherComponentWithName = () => {
  const name = useRecoilValue(nameState);
  return <div>{name}</div>;
}

// useSetRecoilState  
const SomeOtherComponentThatSetsName = () => {
  const setName = useSetRecoilState(nameState);
  return <button onClick={() => setName('Jon Doe')}>Set Name</button>;

 

 

Selector

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

파생된 상태...변화..? 말이 너무 어렵다. 

seletor는 상태에서 파생된 데이터로, 다른 atom에 의존하는 동적인 데이터를 만들 수 있게 해준다.

 

selector를 하나의 상태이지만 파생된 것으로 생각해보자.

selector는 atom로부터 계산된 값을 얻을 수 있고, 또한 복수의 atom에게 영향을 줄 수도 있다. 

 여러 개의 atom으로 부터 계산된 값을 얻을 수 있는 상태값으로 이해하면 된다. 또한 반대로 영향을 줄 수도 있다. 

 

서로 영향을 주는 관계이다. 

 

 

한 번 코드를 봐보자. 이해가 될 수도 있다. 

// 동물 목록 상태
const animalsState = atom({
  key: 'animalsState',
  default: [{
    name: 'Rexy',
    type: 'Dog'
  }, {
    name: 'Oscar',
    type: 'Cat'
  }],
});
// 필터링 동물 상태
const animalFilterState = atom({
 key: 'animalFilterState',
 default: 'dog',
});
// 파생된 동물 필터링 목록
const filteredAnimalsState = selector({
 key: 'animalListState',
 get: ({get}) => {
   const filter = get(animalFilterState);
   const animals = get(animalsState);
   
   return animals.filter(animal => animal.type === filter);
 }
});
// 필터링된 동물 목록을 사용하는 컴포넌트
const Animals = () => {
  const animals = useRecoilValue(filteredAnimalsState);
  return animals.map(animal => (<div>{ animal.name }, { animal.type    }</div>));
}

위의 경우엔 파생된 동물 필터링 목록이라는 파생된 상태를 가지는데 

이게 어디서 파생된 것이냐면 동물 목록 상태와 필터링 동물 상태 두 가지로 부터 파생된 것이다. 

두 가지의 상태를 filter함수로 걸러서 만든 상태이다. 

 

get 키워드로는 다른 atom 상태 값을 가져올 수 있으며 set 키워드로는 자신을 파생시킨 atom 상태값을 변화 시킬 수도 있다. 

 

 

 


참고한 사이트

https://recoiljs.org/ko/

 

Recoil

A state management library for React.

recoiljs.org

https://ui.toast.com/weekly-pick/ko_20200616

 

Recoil - 또 다른 React 상태 관리 라이브러리?

많은 React 상태 관리 라이브러리들이 있고, 가끔 새로운 라이브러리가 등장한다. 그러나 페이스북에서 직접 상태 관리 솔루션을 소개하는 것은 흔하지 않다. 이 라이브러리가 어떤 장점이 있고

ui.toast.com