ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Recoil] 공식문서 파먹기(1) - Recoil에 대하여
    React 2023. 6. 7. 22:56

    본 포스팅은 리코일 공식문서를 기반으로 정리했습니다.

     

    리덕스를 계획한 파트까지 정리를 마무리했습니다.

    저는 리덕스가 가장 많이 사용되는 또는 사용된 상태관리 라이브러리라고 생각하고 있었고 그렇기 때문에 첫 상태관리의 개념을 리덕스의 공식문서를 통해 이해하려 정리했습니다.

     

    리덕스를 정리하며 Flux 패턴의 데이터 플로우를 이해하고 리덕스의 장단점을 살펴봤습니다.

     

    하지만 리덕스는 아무리생각해도 지금 시대에 좋은 라이브러리(더 사용하기 간결하고 직관적인)들이 많이 나와 리덕스를 사용하는것은 조금 비효율적이라고 개인적으로 생각했습니다.

     

    이전 부터 리코일에 관심이 많았기에 리덕스를 정리하고 바로 리코일을 정리해보려 계획했었습니다.

     

    다행이도 리코일의 공식문서는 번역이 완료된 상태라 조금 더 정리하기 수월할것같습니다. 아무래도 영어를 번역하며 정리하는것보다 말도 간결할거구요.

     

    리덕스는 '상태관리'라이브러리로 여러 UI프레임워크(라이브러리)에서 사용이 가능했습니다.

    리코일은 리액트의 상태관리 라이브러리입니다.

     

    동기

    리액트의 상태관리는 useContext로도 라이브러리를 사용하지않고 리액트 내장된 기능으로도 가능합니다.

    하지만 props의 depth가 깊어질수록 관리하기가 어려워진다는 단점이 있습니다.

    그리고 Context는 단일값만 저장이 가능하며 여러 값의 집합을 다룰 수 없다는 단점이 있습니다.

     

    리코일은 이러한 단점들을 커버하기위한 방법으로 선택할 수 있습니다.

    리코일은 아래와같은 특징이 있습니다.

    • 전역상태(global state)를 컴포넌트 내부의 로컬상태(local state)처럼 간단하게 사용가능한 인터페이스(get/set)를 사용하는 보일러플레이트프리 API를 제공합니다
    • 동시성 모드같은 React의 여러 기능들과 호환이 가능합니다
    • 상태의 정의는 점진적이고 분산되어 코드 분할이 가능합니다
    • 상태를 사용하는 컴포넌트를 수정하지 않고도 파생된 데이터로 대체할 수 있습니다
    • 파생된 데이터를 수정하지 않고도 파생된 데이터를 비동기/동기 함수간의 전달이 가능합니다
    • 탐색을 일급개념으로 취급하고 링크상태에서 상태전환을 인코딩할 수 있습니다
    • 전체 애플리케이션 상태를 하위 호환되는 방식으로 유지하기가 쉬우므로, 유지된 상태는 애플리케이션 변경에도 살아남을 수 있습니다

    라고 설명하는데 아직 완전히 이해가 안되는것도, 아예 이해가 안되는것도 있습니다.

     

    차차 정리하면서 이해해보도록 하겠습니다

     

    리코일의 주요 개념

    리코일을 사용하면 atoms(공유상태), selector(순수함수)를 거쳐 리액트 컴포넌트로 흘러가는 데이터 플로우가 형성됩니다.

     

    atoms

    atoms는 상태의 단위(일부)이며, 구독과 업데이트가 가능하며 런타임에 생성될 수 있습니다.

    atoms를 사용하는 컴포넌트는 암묵적으로 atoms를 구독합니다.

    atoms이 업데이트되면 구독한 모든 컴포넌트가 업데이트(리렌더링)되며, 여러 컴포넌트에서 atoms를 공유하고있으면 모두 업데이트됩니다.

    또한 컴포넌트 내부상태로도 사용이 가능합니다.

     

    atoms는 atom 함수를 통해 생성이 가능합니다.

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

    key에는 고유한 식별자(유일한 값)이 들어가야하며 리액트의 상태처럼 기본값을 가질 수 있습니다.

    key값은 전역적으로 중복될 시 오류가 발생하므로 전역적으로 고유한 값으로 지정해줘야합니다.

     

     

    atoms를 사용하기위해 useRecoilState훅을 사용해야합니다.

    function FontButton() {
        const [fontSize, setFontSize] = useRecoilState(fontSizeState);
    
        return (
        	<>
              <button onClick={() => setFontSize(size) => size + 1;}>btn</button>
            </>
        )
    }
    
    function Font() {
        const [fontSize, setFontSize] = useRecoilState(fontSizeState);
        return (
            <p style={{fontSize}}>{'fontSize!'}</p>
        )
    }

     

    확실히 리액트 라이브러리다보니 리액트친화적(?)인 느낌이네요

    useRecoilState로 useState처럼 상태의 기본값을 정의하고 또 이 상태를 여러 컴포넌트에서 공유하며 동시에 업데이트되는 간편한 느낌이 많이 듭니다.

     

    Selectors

    selectors는 atoms에서 파생된 상태의 일부입니다. 파생된 상태는 상태의 변화입니다.

    쉽게 말해서 상태를 순수함수(selectors)로 업데이트하여 변경된 상태라고 이해할 수 있습니다.

    selectors는 selector나 atoms를 인자로 받는 순수함수입니다. 상위의 atoms나 selector가 업데이트되면 하위의 selectors 함수도 다시 실행됩니다.

     

    atoms과 같이 컴포넌트가 selectors를 구독할 수 있으며 상태가 변경되면 컴포넌트가 리렌더링됩니다.

     

    selectors는 상태를 기반으로 파생 데이터를 계산하는데 사용되는 순수함수입니다.

    atoms에 최소한의 상태를 모아놓고 불필요한 상태의 보존을 제거하기위해 selectors를 이용해 업데이트합니다.

     

    그리고 selectors는 자신을 구독하는 컴포넌트를 추적하고 자신이 의존하는 atoms를 추적합니다.

     

    컴포넌트의 관점에서는 selectors와 atoms가 같은 인터페이스를 사용하므로 서로 대체할 수 있습니다.

     

    const fontLabelSizeState = selectors({
        key: 'fontLabelSizeState',
        get: ({ get }) => {
            const size = get(fontSizeState);
            const unit = 'px';
    
            return `${size}${unit}`;
        }
    })

     

    atom과 같이 유니크한 key를 가지며 get 메서드를 갖고있습니다.

    get 필드는 get함수를 인자로 받아 get에서 atom혹은 selector를 인자로 전달하여 참조하는 값에 접근이 가능합니다.

    만약 참조하는 atoms나 selectors가 업데이트되면 해당 get함수도 다시 호출됩니다.

     

    위의 예제코드 selectors fontLabelSizeStatefontSizeState atoms를 의존합니다.

     

    Selectors는 useRecoilValue 훅을 이용해 사용할 수 있습니다.

    useRecoilValue는 하나의 atom이나 selector를 인자로 받아 해당 인자를 대응하는 값을 반환합니다.

    selectors는 writable 하지 않습니다(프로퍼티 디스크립터 writable: false) 즉 읽기 전용 값 이기때문에 useRecoilState로 사용할 수 없습니다.

     

    function FontLabel() {
        const [fontSize, setFontSize] = useRecoilState(fontSizeState);
        const fontSizeLabel = useRecoilValue(fontSizeLabelState);
        
        return (
          <span>{`font size label ${fontSizeLabel}`}</span>
        )
    }

     

    위의 atoms의 예제 코드의 버튼을 클릭하면 어떻게 될까요?

    FontLabel에서 사용하는 selector fontSizeLabel이 해당 atoms를 의존하기때문에 FontLabel의 fontSizeLabel의 값도 같이 업데이트됩니다.

     

     

     

    RecoilRoot

    리덕스에서도 그랬듯 Provider로 최상위 혹은 전역 상태를 공유할 컴포넌트의 최상위를 감아야합니다.

    recoil에서도 RecoilRoot로 컴포넌트를 감아 전역상태를 공유해보겠습니다.

    import React from 'react';
    import {
      RecoilRoot,
      atom,
      selector,
      useRecoilState,
      useRecoilValue,
    } from 'recoil';
    
    function App() {
        return (
            <RecoilRoot>
                <RootComponents />
            </RecoilRoot>
        )
    }

     

    댓글

Designed by Tistory.