프로그램/react

React에 Recoil 상태 관리 라이브러리

대박당 2022. 7. 16. 15:51
728x90
import { v1 } from 'uuid'; 

const selTopMenuState = selector({
  key: "selTopMenuState/${v1()}",
  get: ({ get }) => {
    const topMenuId = get(topMenuState);
    return topMenuId;
  },
});

리액트 상태 관리는 라이프 사이클에 너무 영향을 받으며 정해진 컨트롤 방식도 직관적이지 않습니다.

 

 - 상태 값을 상위 하위 연결성

 - 단일값을 저장하고 관리함

 

 개선하기 위해 Redux, Mobx를 사용하고 있으며, Recoil도 상태관리를 위한 라이브러리입니다.

 

1. Recoil 이란?

페이스북에서 만든 새로운 React를 위한 상태관리 라이브러리 입니다. 최대한 React의 시맨틱과 동작을 유지하면서 위의 문제점을 개선하기 위해 만들어졌고, 다음과 같은 특징을 지녔다고 합니다.

 

 - 공유상태(shared state) 도 React 의 로컬 상태(local state)처럼 간단한 get/set 인터페이스로 사용할 수 있도록 boilerplate-free API를 제공한다.( 필요한 경우 reducers 등으로 캡슐화할 수 도 있다. )

 - 동시성 모드(Concurrent Mode )를 비롯한 다른 새로운 React 의 기능들과의 호환 가능성도 갖는다.

 - 상태 정의는 증분 및 분산되무로 코드 분할이 가능하다.

 - 상태를 사용하는 컴포넌트를 수정하지 않고도 상태를 파생된 데이터로 대체할 수 있다.

 - 파생된 데이터를 사용하는 컴포넌트를 수정하지 않고도 파생된 데이터는 동기식과 비동기식 간에 이동할 수 있다.

 - 탐색을 일급 개념으로 취급할 수 있고 심지어 링크에서 상태 전환을 인코딩 하루 수도 있다.

 - 연호환성 방식으로 전체 애플리케이션 상태를 유지하는 것은 쉬우므로, 유지된 상태는 애플리케이션 변경에도 살아남을 수 있다.

 

 Redux보다 깔끔한 방식이라는 평도 있으니 도전의욕이 생기네요.

 

 2. Recoil 개념

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

(1) Atoms

 atom은 상태의 단위로 값이 업데이트되면 값을 구독(subscribe)한 컴포넌트는 다시 렌더링이 됩니다.

 Atoms는 atom함수를 사용해 생성할 수 있습니다.

const funtSizeState = atom({
   key: 'fontSizeState', // 여기서 key값은 전역적으로 고유해야 합니다.
   default: 14,
});

 

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

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

atom 사용 또다른 예

import { useRecoilValue, useSetRecoilState, useRecoilState } from 'recoil';
import { todoListState } from '...'; <-- 사용자 정의

//for reading
todoList = useRecoilValue(todoListState);

//for writing
setTodoList = userSetRecoilState(todoListState);

//form reading and writing
[todoList, setTodoList] = useRecoilState(todoListState);

 

(2) Selectors (=Derived State)

 Selector는 atoms나 다른 selectors를 입력으로 받아들이는 순수 함수(pure function) 입니다. 상위의 atoms 또는 selectors가 업데이트되면 하위의 seletor함수도 다시 실행되죠

 

 Selectors는 selector함수를 사용해 정의 됩니다.

 

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

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

사용예)

import { selector} from 'recoil';

//selector is used as follows ( usually written in a separate file )
const filteredTodoListState = selector({

	// selector`s unique key
    key : 'filteredTodoListState',
    
    // selector`s logic
    get: ({ get }) => {
    	const filter = get(todoListFilterState); //subscribe todoListFilterState
        const todoList = get (todoListState);    //subscribe todoListState
        
        switch (filter) {
        	case 'show Completed':
            	return todoList.filter((todoItem) => todoItem.isCompleted);
            case 'show Uncompleted':
            	return todoList.filter((todoItem) => !todoItem.isCompleted);
            default:
            return todoList;
        }
   }
);
/* 위에 정의된 selector는 다음과 같이 Hook을 통해 사용된다. 
이때 Atom에서 사용한 useSetRecoilState(), useRecoilState()  Hook을 사용할 수 없다.
이는 Writeable Selector(set 프로퍼티를 가진 객체로 정의되는 Selector)가 아니기 때문이다.
*/

import {useRecoilValue } from 'recoil';
import { filteredTodoListState } from '...';

//for reading
filteredTodoList = useRecoilValue ( filteredTodoListState);

 

 

3. Recoil 설치하고 사용해보기

 우선 recoil 패키지를 설치합니다.

>> npm install recoil.  // 혹은 yarn add recoil

 

 그리고 recoil 상태를 사용하는 부모 컴포넌트는 부모 트리 어딘가에 RecoilRoot가 필요한데요, 일반적으로 루트 컴포넌트에 넣습니다.

 

//  기존 루트 파일 (_app.tsx)
import type { AppProps } from "next/app"
import "../styles/globals.css"

function MyApp({ Component, pageProps }: AppProps) {
   return <Component {...pageProps} />
}

export default MyApp
// 수정한 루트 파일
import type { AppProps } from "next/app"
import "../styles/globals.css"
inport { RecoilRoot, atom, selector, useRecoilState, useRecoilValue } from "recoil"

function MyApp({ Component, pageProps }: AppProps){
   return (
    <RecoilRoot>
       <Component {...pagheProps} />
    </RecoilRoot>
   )
 }
 export default MyApp

이제 한 번 위에서 배운 atoms과 selectors을 사용해보기 위해 다음과 같은 코드를 추가해보겠습니다.

 

atom을 통해 string을 담을 textState를 만들고, selector로 글자 수를 리턴할 charCountState를 선언했습니다.

그리고 useRecoilState와 useRecoilValue를 통해서 선언한 값들을 사용해 보았습니다.

import React from "react";
import { ChangeEvent } from "react";
import { atom, selector, useRecoilState, useRecoilValue } from "recoil";

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

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

const TestPage = () => {
  const [text, setText] = useRecoilState(textState);

  const onChange = (event: ChangeEvent<HTMLInputElement>) => {
    setText(event.target.value);
  };

  const count = useRecoilValue(charCountState);

  return (
    <div style={{ padding: "3rem" }}>
      <h2
        style={{
          fontSize: "2rem",
          marginBottom: "1rem",
        }}
      >
        DemoM
      </h2>
      <input type="text" value={text} onChange={onChange} />
      <br />
      Echo:{text}
      <br />
      Character Count: {count}
    </div>
  );
};
export default TestPage;

화면 재조회하면서 스토어 재생성을 하는 것 같고 예외처리와 키중복 제외 방식으로 개발하고 있는것 같다. 

최종 중복 생성을 막는 것이 맞을 것 같지만 UUID를 사용해서 키중복을 막는 방법 자체는 알고 있으면 좋을듯 싶어서 적어 봅니다.

npm i --save-dev @types/uuid

 

 

 

728x90