Recoil 기초

ToastEggsToast·2021년 7월 4일
0

TIL

목록 보기
11/15

Recoil

What is Recoil

Recoil은 Redux, Mobx 등과 같은 상태 관리 라이브러리 중 하나입니다. ReactJs를 제작한 페이스북에서 직접 소개한 솔루션이기도 합니다.

기존의 리액트 상태관리 라이브러리는 Store이라는 곳에 상태를 저장, 관리하고 있었는데 Store는 외부 요인으로 취급되어 리액트의 내부 스케줄러에 접근할 수 없게 된다는 단점이 있었습니다.
(리액트 내부 스케줄러: 리액트 컴포넌트에 업데이트가 존재할 경우, 브라우저의 상태와 조건을 기반으로 실행할 "시점"을 판단합니다.)

이러한 단점을 해결하고자 생겨난 상태관리 라이브러리이며, 리코일은 데이터 흐름 그래프의 형태로 리액트 컴포넌트의 트리 구조에 붙어있게 만들어 줍니다. 트리 구조에 붙어있는 "Recoil Root"에서부터 상태 변화가 일어나는데, 이 루트를 "아톰(atom)"이라고 합니다. 이러한 상태 변화의 과정은 순수 함수는 "셀렉터(selector)"를 통해 일어납니다. 이러한 접근을 통해 리코일은 다음과 같은 특징을 지니게 됩니다.

  • 보일러 플레이트가 없는 api로 리액트 지역 상태로서 단순한 get/set 인터페이스로 상태를 공유할 수 있습니다.
  • 동시성 모드와 양립할 수 있는 가능성이 있으며 새로운 리액트의 특성이 가능하게 합니다.
  • 상태 정의는 증가, 분산되어 코드 스플리팅이 가능해집니다.
  • 상태는 컴포넌트의 변경 없이 파생된 상태로 대체 될 수 있습니다.

Core Concepts

Atom

아톰은 리코일의 "상태 단위"입니다. 업데이트가 가능하고 읽어들임(구독)이 가능합니다.
아톰을 업데이트하게 될 경우 해당 아톰을 구독하고 있는 컴포넌트들은 리랜더링을 일으키고, UI에 변화를 일으킬 수 있게 합니다.

const todoListFilterState = atom({
  key: 'todoListFilterState',
  default: 'show All'
});

아톰은 위와 같은 형태로 작성됩니다.
key는 아톰을 구분하는 고유의 아이디(unique Id/unique key)값으로, 겹치지 않도록 유의하며 작성해줘야 합니다.
default는 해당 atom의 초기 값으로, useState( )에서 괄호 안에 들어가는 것 처럼 값의 형태가 들어가게 됩니다.

Typescript와 함께 아톰을 작성 할 경우, 다음과 같이 작성할 수 있습니다.

const todoListFilterState = atom<string>({
  key: 'todoListFilterState',
  default: 'show All'
});
});

Selectors

셀렉터(selector)는 순수 함수로, derived State를 나타냅니다. 아톰이나 다른 선택자를 입력값으로 받습니다. 아톰이나 선택자가 업데이트가 되면, 함수는 다시 계산되어 새로운 값을 리턴합니다.
아톰과 같이 컴포넌트는 셀렉터를 import해 구독할 수 있고, 셀렉터가 업데이트 될 경우 리랜더링이 발생됩니다.

const filteredTodoListState = selector({
  key: 'filteredTodoListState',
  get: ({get}) => {
  	const filter = get(todoListFilterState); // 필터하고자 하는 "조건" 에 대한 atom
    const list = get(todoListState); // 전체 todoList가 담긴 atom
    
    switch (filter) {
      case 'Show Completed':
        return list.filter((item) => item.isComplete);
      case 'Show Uncompleted':
        return list.filter((item) => !item.isComplete);
      default:
        return list; 
    } // return 된 list들을 "filteredTodoListState"에 저장합니다.
  },
});

셀렉터는 위와 같은 형태로 정의되며, get 뿐만 아니라 set property도 사용할 수 있습니다.
get의 경우 입력값으로 받은 아톰이나 선택자가 변경 될 경우 해당 값에서 값을 받아올 때에, set은 셀렉터에 저장되어있는 값을 컴포넌트로 내려줄 때 실행됩니다.

Asynchronous Selector

Query 등을 통해 데이터를 얻어와야 하는 경우에도 selector를 통해 데이터를 저장해둘 수 있습니다.

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

error가 발생했을 경우, <ErrorBoundary></ErrorBoundary>로 감싸 에러 핸들링이 가능합니다.

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

function CurrentUserInfo() {
  const userName = useRecoilValue(currentUserNameQuery);
  return <div>{userName}</div>;
}

function MyApp() {
  return (
    <RecoilRoot>
      <ErrorBoundary>
        <React.Suspense fallback={<div>Loading...</div>}>
          <CurrentUserInfo />
        </React.Suspense>
      </ErrorBoundary>
    </RecoilRoot>
  );
}

asynchronous Selector With Parameters

selector에 파라미터 값을 넘겨 asynchronous한 처리를 진행해야 하는 경우, selectorFamily를 통해 진행할 수 있습니다.

const userNameQuery = selectorFamily({
  key: 'UserName',
  get: userID => async () => {
    const response = await myDBQuery({userID});
    if (response.error) {
      throw response.error;
    }
    return response.name;
  },
});

function UserInfo({userID}) {
  const userName = useRecoilValue(userNameQuery(userID));
  return <div>{userName}</div>;
}

function MyApp() {
  return (
    <RecoilRoot>
      <ErrorBoundary>
        <React.Suspense fallback={<div>Loading...</div>}>
          <UserInfo userID={1}/>
          <UserInfo userID={2}/>
          <UserInfo userID={3}/>
        </React.Suspense>
      </ErrorBoundary>
    </RecoilRoot>
  );
}

이 외에도 다양한 api들이 존재합니다.

https://recoiljs.org/docs/guides/asynchronous-data-queries

Recoil Hooks

ReactJs와 마찬가지로 Recoil 또한 여러가지 Hook들을 가지고 있습니다. 그 중 가장 대표적으로 쓰이고 있는 세 가지에 대해 설명하고자 합니다.

useRecoilState()

useRecoilState의 사용법은 ReactJs의 useState와 흡사합니다.

const UserNameAtom = atom({
  key: 'userName',
  default: '',
});

const Name = () => {
  const [userName, setUserName] = useRecoilState(UserNameAtom);
  
  return (
    <>
      <input onChange={(e) => setUserName(e.target.value)}/>
      <span>{userName}</span>
	</>
  )
};

useRecoilState의 파라미터로 atom을 넘기면 저장된 atom값과, set할 함수를 리턴합니다.

useRecoilValue()

useRecoilState와는 다르게 저장된 값을 받아오기만 할 수 있습니다.
set을 할 수 없습니다.

const UserNameAtom = atom({
  key: 'userName',
  default: '',
});

const Name = () => {
  const userName = useRecoilValue(UserNameAtom);
  
  return (
    <>
       <p>
    	   <span>{userName}</span>회원님, 반갑습니다!
       </p>
	</>
  )
};

useSetRecoilState()

오직 set만 하는 함수를 리턴합니다.

const UserNameAtom = atom({
  key: 'userName',
  default: '',
});

const Name = () => {
  const setUserName = useSetRecoilState(UserNameAtom);
  
  return (
    <>
       <input onChange={(e) => setUserName(e.target.value) }/> 
	</>
  )
};

How to Start

Installation

npm install recoil 혹은 yarn add recoil을 통해 설치하거나, <script src="https://cdn.jsdelivr.net/npm/recoil@0.0.11/umd/recoil.production.js"></script> CDN을 통해 사용할 수 있습니다.

RecoilRoot

atom이 가지는 값을 저장, 전달합니다. 컴포넌트들의 최상단에 위치해야 합니다.

import {RecoilRoot} from 'recoil';

function AppRoot() {
  return (
    <RecoilRoot>
      <ComponentThatUsesRecoil />
    </RecoilRoot>
  );
}
profile
개발하는 반숙계란 / 하고싶은 공부를 합니다. 목적은 흥미입니다.

0개의 댓글