ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Recoil] 전역 상태관리 라이브러리 - Recoil 정복기
    Front-End(Web)/React - 라이브러리들 2021. 12. 30. 01:04
    반응형

    🧐 서론

    굉장히 오랜만에 쓰는 서론인 것 같다!! 그만큼 이 글의 길이가 짧진 않을거라는 마음의 준비 차원일지도?

     

    오랜만에 React를 복기하고 Typescript를 숙달할 겸 예전에 면접과제로 받았던 메모장 어플을 다시 만들어보았다.

    전역 상태관리 라이브러리 역시 사용하고자 했고, 이전 Redux의 불필요한 구성과 복잡한 원리에 학을 뗀지라..

    페이스북에서 출시한 React 전용 전역 상태관리 라이브러리인 Recoil을 이번 기회에 학습 & 사용해보고자 결심했다!

     

    우선, 두괄식으로 결론을 내리자면, 내가 앞으로 React 프로젝트를 작업한다면 왠만하면 Recoil이다!

    그만큼, Redux처럼 다양한 구성(action, reducer 등)을 할 필요가 없으며, 특히 비동기 요청이 매우 심플하다.

    (Redux의 경우 비동기 요청을 위한 Redux-thunk, Redux-Saga 등의 서드파티 라이브러리가 필요했음!)

     

    또한, Recoil 내에서 테마를 나눌 필요가 없을 만큼 사용법이 매우 심플하기 때문에,

    이 포스팅 하나에 Recoil의 개요, 사용법, 그리고 토이를 진행하며 느낀 나의 소감까지 한 번 정리해보도록 하겠다!


    🧡 Recoil 이란?

    Recoil은 React 프로젝트를 위한 많은 전역 상태관리 라이브러리들 중 하나로, 2020년 5월 Facebook에서 출시하였다.

    그렇기에, 다른 라이브러리(Redux, Mobx)와는 달리 React 전용이며 React에 최적화되어 있다고 할 수 있다.

     

    - 이전 전역 상태관리 라이브러리들의 문제?

    대표적으로 사용됬던 Redux, Mobx들의 성능 자체에 문제가 있었던 것은 아니다.

    오히려, 페이스북에서 고안한 Flux 패턴을 기반으로 설계되면서 안정적인 전역상태 관리가 가능하였다. (Flux 패턴 관련링크)

    참고 : Flux 패턴

     

    그럼에도 불구하고, Redux의 사용도와 만족도가 감소하게 된 데에는 아래와 같은 문제들이 잔존하였다.

    • React 전용 라이브러리가 아니다! React 관점에선 외부요인으로 Store가 취급되며, 동시성 모드를 구현하기에 호환성이 부족하다.
    • 복잡한 Boiler Plate 초기세팅이 요구된다! Store, Action, Reducer 등 다양한 구성요소가 필요해 비효율적이며 러닝커브가 높다.
    • 비동기 데이터에 추가 리소스가 요구된다! Redux-saga 등 전역상태에 비동기 데이터를 호출하기 위한 서드파티 라이브러리가 필요하다.

    이러한 단점들을 보완한, 그리고 React에 좀 더 최적화된 전역 상태관리 라이브러리라는 사명을 가지고 등장한 것이 바로 Recoil 이다!

     

     

    - Recoil 출시 배경

    Recoil은 우선 React 전용 라이브러리인 만큼 React 내부 접근성이 용이하다. 

    특히, React 동시성 모드, Suspense 등을 지원하기 때문에 사용자 경험 관점에서도 매우 유리한 웹 어플리케이션을 만들 수 있다.

     

    또한, 전역상태의 설정/정의가 매우 쉬우며, Recoil이 지원하는 Hooks로 이를 get/set 하기 때문에 React 문법과 매우 유사하다.

    전역상태를 사용하기 위한 Boiler Plate 양이 현저히 적으며(recoil 디렉토리만 필요), 사용하는 방법도 Hooks 기반으로 매우 심플하며 React스럽기 때문에 러닝커브가 낮다는 장점이 있다.

     

    Recoil 공식문서에서는, 출시한 동기에 대해 아래와 같이 정리한다.

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

     

    🧡 Recoil 시작하기

    - 설치 및 적용

    // npm(yarn)
    npm install recoil
    yarn add recoil
    
    // CDN
    <script src="https://cdn.jsdelivr.net/npm/recoil@0.0.11/umd/recoil.production.js"></script>

    설치는 위처럼 npm(yarn) 혹은 CDN 2가지 방식으로 적용할 수 있다.

    * Recoil은 Webpack과 같은 모듈 번들러와 호환이 되며, ES6 ➡️ 5로는 트랜스파일링 되지 않기 때문에 크로스 브라우징을 위해 Babel을 통한 코드 컴파일 과정이 요구된다.

     

    프로젝트에 적용하는 방법은 아래와 같다.

    // index.tsx
    
    import React, { StrictMode } from 'react';
    import ReactDOM from 'react-dom';
    import { RecoilRoot } from 'recoil';
    import './index.css';
    import App from './App';
    
    ReactDOM.render(
      <StrictMode>
        <RecoilRoot>	// Recoil Root 적용!
          <React.Suspense fallback={<div>Loading... </div>}>
            <App />
          </React.Suspense>
        </RecoilRoot>
      </StrictMode>,
      document.getElementById('root'),
    );

    먼저, Recoil을 활용하기 위해 index.tsx 최상단의 <App /> 컴포넌트를 <RecoilRoot> 로 감싸주기만 하면 된다! (매우 쉽다.)

     

     

    그리고, /src 내 recoil 폴더를 만들어서 여기에 전역상태에 관련된 Atoms, Selector 들을 설정한다.

     

     

    - 주요개념

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

    Atoms는 컴포넌트가 구독할 수 있는 상태의 단위다. Selectors는 atoms 상태값을 동기 또는 비동기 방식을 통해 변환한다.

    또한, Recoil에서 지원하는 Hooks들로 atom 혹은 selecter의 get(접근) 및 set(수정) 등의 다양한 동작이 가능하다.

     

     

    1. Atoms

     

    Atoms는 Recoil 상태의 단위를 의미한다. 컴포넌트간에 이 상태는 공유되며, 구독 및 업데이트가 가능하다.

    특히, atom의 상태가 업데이트되면, 이를 구독하던 컴포넌트들이 모두 리렌더링된다.

    * Redux는 Reducer(통상 상위 도메인) 단위로 state를 구성하나, Recoil은 Atoms(상태) 단위로 좀 더 잘게 구성할 수 있다.

     

    Atoms을 설정할 땐, Recoil의 atom() 메서드를 통해 변수에 할당해주면 된다. 이 때, key, default 2개의 프로퍼티를 필수로 설정해야한다.

    • key : 고유한 key 값 (보통 해당 atom을 생성하는 변수 명으로 지정합니다.)
    • default : atom 의 초기값을 정의합니다. 정적인 값(int, string...), promise, 다른 atom 의 값으로 설정할 수 있습니다.
    // /src/recoil/index.ts
    
    import { atom } from 'recoil';
    
    export const focusMemoState = atom<MemoItem>({
      key: 'focusMemo',
      default: null,
    })
    
    export const checkedMemoListState = atom<string[]>({
      key: 'checkedMemoList',
      default: [],
    })

    이는, 내 토이 프로젝트에서 focus된 메모(Object), 체크된 메모들의 id배열을 각각 전역상태로 사용하기 위해 atom을 설정한 모습이다.

    key는 중첩되지 않게 설정해야하며, default 값 역시 부여해준 모습이다.

    * default 값은 Promise 객체도 설정가능하나, atom에서 바로 비동기 요청을 할 순 없다. 이는, 이후 Selector에서 배워보자!

     

     

     

    2. 전역상태 관련 Hooks

     

    전역상태(Atoms, Selector)를 get/set 하기 위해 Recoil에서 제공하는 Hooks들을 사용한다. 기본적으로 아래 4가지가 크게 사용된다.

    • useRecoilState() : useState() 와 유사하다. [state, setState] 튜플에 할당하며, 인자에 Atoms(혹은 Selector)를 넣어준다.
    • useRecoilValue() : 전역상태의 state 상태값만을 참조하기 위해 사용된다. 선언된 변수에 할당하여 사용하면 된다.
    • useSetRecoilState() : 전역상태의 setter 함수만을 활용하기 위해 사용된다. 선언된 함수변수에 할당하여 사용하면 된다.
    • useResetRecoilState() : 전역상태를 default(초기값)으로 Reset 하기 위해 사용된다. 선언된 함수변수에 할당하여 사용하면 된다.
    // 예시
    
    import { useRecoilState, useSetRecoilState, useResetRecoilState } from 'recoil';
    import { countState } from '../../recoil/count';
    
    function ReadWriteCount() {
      const [ count, setCount ] = useRecoilState(countState); // useRecoilState 을 통한 value, setter 반환
      const countValue = useRecoilValue(countState); // 구독하는 atom 의 값만 반환
      const setCountUseSetRecoilState = useSetRecoilState(countState); // 값을 변경하는 함수만 반환
      const resetCount = useResetRecoilState(countState); // 설정된 기본값으로 리셋
        
      return (
        <div>
          <h2>읽기 쓰기 카운트 컴포넌트</h2>
          
          <p>카운트 {count}</p>
          <p>카운트(useRecoilValue 사용) {countValue}</p>
          
          <button onClick={() => setCount(count + 1)}>숫자 증가</button>
          <button onClick={() => setCount(count - 1)}>숫자 감소</button>
          <button onClick={() => setCountUseSetRecoilState(count + 1)}>숫자 증가 (useSetRecoilState 사용)</button>
          <button onClick={() => setCountUseSetRecoilState(count - 1)}>숫자 감소 (useSetRecoilState 사용)</button>
          <button onClick={resetCount}>카운트 리셋</button>
        </div>
      );
    }
    
    export default ReadWriteCount;

     

     

     

    3. Selector

     

    Selector는 atom 혹은 다른 Selector 상태를 입력받아 동적인 데이터를 반환하는 순수함수(Pure Function) 이다.

    상태값에서 비롯된 파생된 데이터를 만들 때 사용되며, atom처럼 컴포넌트가 이를 구독할 수 있다. (readonly 값이므로 useRecoilValue)

    Selector가 참조하던 다른 상태가 변경되면 이도 같이 업데이트되며, 이 때 Selector를 바라보던 컴포넌트들이 리렌더링 되는 것이다.

     

    Selector를 설정할 때도, Recoil의 selector() 메서드를 통해 등록하면 된다. 기본적으론, key와 get 2개의 프로퍼티를 설정한다.

    • key : 고유한 key 값
    • get : Selector 순수함수. 사용할 값을 반환하며, 매개변수인 콜백객체 내 get() 메서드로 다른 atom 혹은 selector를 참조한다.
    export const memoListSelector = selector<MemoList>({
      key: 'memoList',
      get: async ({ get }) => {
        const id = get(focusLabelState)?.id;
        const totalList = get(totalMemoListSelector);
        return id ? getMemoListByLabel(id) : totalList;
      }
    })

    마찬가지로 토이에서 메모목록 배열값을 반환하는 Selector를 만들었다.

    id는 포커스된 라벨의 아이디(atom) / totalList는 전체 메모리스트(selector)를 각각 가져온 뒤,

    아이디가 있으면 라벨 아이디에 해당하는 메모리스트를 / 없다면 전체 메모리스트를 동적으로 반환하는 selector 인 것이다.

     

    이렇게 만들었을 때 장점은, focusLabel의 아이디가 바뀔 때마다 이 selector도 동적으로 해당하는 리스트를 반환해주는 부분이다.

    Selector는 위처럼 파생된 데이터를 사용하는 목적성 외에도, 유용한 사용성들이 몇 가지 존재한다.

     

     

    * set 함수(프로퍼티) : 쓰기 가능한 Selector

     

    Selector에 set을 설정하게 되면, 쓰기 가능한 모드로 변경된다. set은 콜백객체, 새로운 값 2가지를 각각 매개변수로 받는다.

    콜백객체 내에는 마찬가지로 set() 메서드가 존재하며, 이는 다른 atom들을 새로운 값으로 세팅하기 위해 사용된다.

    (set 메서드를 쓸 때 유의할 점은 본인 스스로를 수정할 수 없다는 것이다! 본인을 갱신하기 위해선 추가적으로 의존성을 설정하는데 이 방법은 더 아래에 소개하겠다.)

    const proxySelector = selector({
      key: 'ProxySelector',
      get: ({get}) => ({...get(myAtom), extraField: 'hi'}),
      set: ({set}, newValue) => set(myAtom, newValue),
    });

    이처럼, set() 메서드는 상태변수, 값 2가지를 매개변수로 받는다. 또한, 해당 상태값을 newValue로 갱신해주는 역할을 한다.

    컴포넌트에서 이 set을 사용할때는, useRecoilState() Hooks로 Selector를 가져오면 setter 함수가 이 set()의 역할을 하는 것이다.

    // 컴포넌트 예시
    
    // ...
      const [proxy, setProxy] = useRecoilState(proxySelector);
      
      setProxy('새로운 값!');
    }

     

     

    * 비동기 Selector

     

    어쩌면, Recoil의 강점 중 하나이자 상태값에 Selector가 많이 쓰이는 이유일 것이다. 

    API 등 비동기 요청을 한 데이터를 전역상태에 넣는 경우가 종종 있다. 이 때, 이 비동기 호출을 내부에 설정해 상태값으로 반환하는 역할 역시 Selector가 충실하게 수행해준다!

    const myQuery = selector({
      key: 'MyQuery',
      get: async ({get}) => {
        return await myAsyncQuery(get(queryParamState));	// 비동기 호출 부분
      },
    });

    유의해야 할 부분은, 비동기 Promise 객체를 반환하기 때문에 async/await 문을 적용해야 하는 부분이다.

    또한, 이렇게 비동기 Selector만 사용하면 아래와 같이 에러가 발생하는데, 이는 Suspense에 대한 처리를 하지 않아서 발생한 것이다.

     

    Recoil은 비동기 처리를 React의 suspense를 통해 지원한다. 위 에러는, 비동기 호출간 노출할 fallback UI가 존재하지 않아 발생했다.

    그렇기에, 비동기 호출이 사용되는 컴포넌트를 <React.Suspense>로 랩핑해주고, fallback으로 호출할 컴포넌트를 설정해주면 된다!

    <React.Suspense fallback={<div>Loading...</div>}>
      <Component />
    </React.Suspense>

     

     

    * Loadable : 또 다른 비동기 제어 방법

     

    Suspense 외에 다른 방법으로 Loadable Hooks(useRecoilValueLodable, useRecoilStateLodable) 을 사용하는 방법이 있다.

    Lodable 객체state(비동기 처리상태), contents(상태값) 2가지 프로퍼티를 반환한다. (Promise 패턴과 유사)

    • state : hasValue , hasError , loading 3가지 상태를 반환한다.
    • contents : atom이나 contents의 상태값을 의미한다. hasValue 상태일 땐 value를, hasError 일 땐 Error 객체를, 그리고 loading 일 땐 Promise를 가지고 있다.
    import { useRecoilValueLodable } from 'recoil';
    import { lodaSelector } from '@/src/recoil';
    
    const Component = () => {
      const lodaState = useRecoilValueLodable(lodaSelector);
      
        switch(lodaState.state){
          case 'hasValue':
            return <div>{lodaState.contents}</div>
          case 'loading':
            return <Loading />;
          case 'hasError':
         	throw lodaState.contents;
        }
    }

     

     

    * 캐싱 : Selector를 통한 성능 향상

     

    selector는 기본적으로 값을 자체적으로 캐싱한다. 입력된 적 있는 값을 기억하고, 이 값이 재호출되면 캐싱된 결과를 바로 보여주기 때문에 비동기 데이터를 다루는 측면에서 유리하다.

     

     

     

    4. atomFamily(), selectorFamily()

     

    이 Family 메서드들은 atom(혹은 selector)을 리턴하는 팩토리 함수이다. 인자(params)를 받아, 이를 반영한 동적인 상태값을 반환한다.

    그렇기에, Recoil 내 다른 상태값이 아닌, 외부인자(Query Params 등)를 활용한 상태값을 관리하기에 유리하다.

    // /recoil/index.ts
    
    const todoItemState = atomFamily<Todo, string>({
      key: 'todoItemState',
      default: (id) => {
        return {
          id,
          title: '',
          isDone: false,
        };
      },
    });
    // TodoItem.tsx
    
    const TodoItem: React.FC<Props> = ({ id }) => {
      const [todo, setTodo] = useRecoilState(todoItemState(id));
      
      // ...
    }

     

    * 파라미터를 반영한 비동기 데이터 : selectorFamily()

     

    이처럼, Family의 get, set은 매개변수로 params 객체를 받는다. 이를 반영한 비동기 데이터를 상태값으로 쓰기 위해 selectorFamily가 사용되는 것이다.

    // /recoil/index.ts
    
    export const githubRepo = selectorFamily({
      key: "github/get",
      get: (githubId) => async () => {
        if (!githubId) return "";
    
        const { data } = await axios.get(
          `https://api.github.com/repos/${githubId}`
        );
        return data;
      },
    });
    // Components.jsx
    
    import { useRecoilValue } from 'recoil';
    import { selectorFamily } from '../../state';
    const Github = () => {
      const githubId = 'juno7803';
      const githubRepos = useRecoilValue(githubRepo(githubId));
      
      return(
        <>
          <div>Repos : {githubRepos}</div>
        </>
      )
    
    }
    export default Github;

     

     

    * Trigger

     

    이 내용은 Docs 혹은 공식적으로 소개되는 내용은 아니나, 내가 Selector의 상태갱신에 대해 고민하다가 배우게 된 부분이다.

    이는 내가 진행한 토이 프로젝트의 브라우저 화면이다. (메모장 어플리케이션)

    컴포넌트들은 Recoil 전역상태를 공유하고 있으며, 위처럼 라벨을 추가하고 해당 라벨에 메모들을 등록하는 동작을 만들었다.

     

    만약, 가운데의 <MemoList> 컴포넌트에서 메모들을 체크하고 이를 라벨에 추가한다면, 좌측의 <LabelList>의 해당 라벨의 메모숫자도 갱신되어야 하는 이슈가 생길 것이다.

     

    이 리스트들은 서버 데이터에서 가져오기 때문에 Selector를 사용했고, 이는 readonly한 값이기에 직접 수정할 수 없다.

    그렇다고, set를 통해 일일히 갱신된 값을 전달해서 상태값을 수정한다거나, 페이지 자체를 reload 해버리는 것은 적절하지 않다.

     

    selector가 비동기 데이터를 다시 불러와 갱신할 방법을 고민했고, 이를 구현한 방법과 글을 소개하고자 한다.

     

    // /recoil/index.ts
    
    import { atom, atomFamily, selector } from 'recoil';
    import { getLabelList, getMemoList } from "services";
    import { TriggerParams } from 'types/recoil';
    import { LabelItem, LabelList, MemoItem, MemoList } from '../types/data';
    
    export const selectorTrigger = atomFamily<number, TriggerParams>({
      key: 'selectorTrigger',
      default: Date.now(),
    })
    
    export const labelListSelector = selector<LabelList>({
      key: 'labelList',
      get: async ({ get }) => {
        get(selectorTrigger('labelList'))
        const list = await getLabelList();
        return list;
      },
      set: ({ set }) => {
        set(selectorTrigger('labelList'), Date.now())
      }
    })
    
    export const memoListSelector = selector<MemoList>({
      key: 'memoList',
      get: async ({ get }) => {
        get(selectorTrigger('memoList'));
        const totalList = get(totalMemoListSelector);
        const id = get(focusLabelState)?.id;
        return id ? getMemoListByLabel(id) : totalList;
      },
      set: ({ set }) => {
        set(selectorTrigger('memoList'), Date.now())
      }
    })
    
    // ...
    1. selectorTrigger 라는 atomFamily를 만들었다. 여기서 반환하는 atom들의 Default 값은 생성된 시점의 시간이다.
    2. 트리거를 사용하는 selector들은 get에서 트리거를 get해와서 구독하고 있어야 한다.
    3. 트리거 atomFamily의 atom key명은 각 selector 명으로 한다. (selectorTrigger('LabelList') 등)
    4. set은 selectorTrigger의 해당 selector의 트리거 atom을 갱신한다. 그러면, 이를 구독하던 selector가 데이터를 재호출한다.

    트리거를 갱신하는 방법은, Date.now() 외에도 count++ 등 자유롭게 구현하면 된다.

    이 트리거는 원리가 비슷한 atom이 양산되기 때문에, 여러 개를 만드는것보다 atomFamily로 묶는게 생산성과 유지보수에 유리하다.

     

    다음으로, 이 set 함수를 할당해서 사용한 컴포넌트의 코드를 소개하겠다.

    const MemoListHeader: React.FC = () => {
      /* Recoil State */
      const labelList = useRecoilValue<LabelList>(labelListSelector);
      const checkedMemoList = useRecoilValue<string[]>(checkedMemoListState);
      const [focusLabel, setFocusLabel] = useRecoilState<LabelItem>(focusLabelState);
    
      /* Recoil Reseter */
      const updateLabelList = useResetRecoilState(labelListSelector);	// * Selector setter
      const resetCheckedMemoList = useResetRecoilState(checkedMemoListState);
    
      const attachLabel = async () => {
        if (!checkedMemoList.length) {
          alert('메모를 먼저 선택해주세요!');
          return;
        }
        const title = await window.prompt();
        const label = labelList.find(e => e.title === title)
        if (!label) return;
        await postAttachLabel({ id: label.id, memoIds: checkedMemoList });
        resetCheckedMemoList();
        updateLabelList();
        setFocusLabel(label);
      }
      
      // ... 
    }
    1. attachLabel() 메서드체크된 메모들에 라벨을 부여하는 메서드다.
    2. 체크 메모목록 확인 ➡️ 라벨명 수집 ➡️ 라벨 탐색 ➡️ 라벨 추가(API) ➡️ 체크 메모목록 초기화 ➡️ 라벨목록 갱신 ➡️ 포커스 라벨 변경
    3. updateLabelList() useResetRecoilState() 로 Selector의 set 함수를 가져와 할당한 변수이다.
    4. 이를, 메서드 내에서 필요한 부분에서 호출시켜 labelList 데이터를 갱신해준다.

    코드처럼, selector의 set 함수는 useResetRecoilState() Hooks로 구현한다. 이 함수를 호출하면, set 부분이 실행되는 것이다.

    (memoList 역시 갱신되어야 하나, 이는 위 예시에서 생략된 useEffect를 통해 제어하였다.)

     

    이 방법이 정답은 아닐 것이다. 어쨌든 매 갱신이 서버요청을 전송하므로, 데이터가 많아질수록 네트워크 부하가 생길 것이다.

    하지만, 노션과 같이 공통 사용자의 경우를 가정했을 때, 최신상태를 업데이트하는 직관적이고 안전한 방법이라는 장점도 있을 것이다.

     

    이외에도, useRecoilCallback() 콜백과 스냅샷, waitForAll() 동시성 helper 등 다양한 API들을 제공하고 있다. (공식문서 링크)


    😃 마무리하며

    Recoil에 대한 짧지만 나름 힘겨웠던(?) 포스팅이 마무리되었다! 이전, Redux 학습(포스팅)을 생각했을 때, 리소스가 현저히 적게 들었다!
    Recoil을 사용한 경험은 적지만 매우 많은 장점과 매력을 느꼈다. (심플한 세팅, 간단하고 React 친화적인 문법, 쉬운 비동기 요청 등)

     

    하지만, Recoil을 과감히 도입하기에는 일부 단점들이 존재한다.

    • 개발자 도구가 완벽하지 않다. 디버깅 및 스냅샷 테스트를 하는데 있어 신뢰성이 부족하다.
    • 모든 API들이 높은 신뢰성을 보장하지 않는다. useGetRecoilValue, useRecoilRefresher 등은 공식문서도 UNSTABLE로 분류.

     

    그렇지만 위 부분들을 어느정도 감수하더라도 나는 앞으로 React 프로젝트에 Recoil을 사용할 것 같다.

    부족한 글이지만, 이를 읽은 분들이 내가 느낀 Recoil의 강점들을 공감하시기를 바라며 이만 이번 포스팅도 마무리하겠다!

     

     

    📎 출처

    - [Recoil] Recoil 공식문서 : https://recoiljs.org/ko/docs/introduction/getting-started  

    - [Recoil] junoflog 님의 블로그 : https://velog.io/@juno7803/Recoil-Recoil-200-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0  

    - [Recoil] woolta 님의 블로그 : https://blog.woolta.com/categories/1/posts/209

     

    반응형
Designed by Tistory.