[React] 상태관리 라이브러리(Recoli)

rondido·2022년 12월 8일
0

React

목록 보기
36/40
post-thumbnail

Recoil의 탄생

React에는 다음과 같은 한계가 존재

  • 컴포넌트의 상태는 공통된 상위요소까지 끌어올림으로써 공유될 수 있지만, 이 과정에서 거대한 트리가 다시 렌더링되는 효과를 야기하기도 한다.
  • Context는 단일 값만 저장할 수 있으며, 자체 소비자를 가지는 여러 값들의 집합을 담을 수는 없다.
  • 이 두가지 특성이 트리의 최상단(state가 존재하는 곳)부터 트리의 잎(state가 사용되는 곳)까지 코드 분할을 어렵게한다.

Recoli은 직교하지만 본질적인 방향 그래프를 정의하고 React 트리에 붙인다. 상태 변화는 이 그래프의 뿌리(aotms라고 부르는)로부터 순수함수를 거쳐 컴포넌트로 흐르는 다음과 같은 접근 방식을 따른다.

  • 우리는 공유상태(shared state)도 React의 내부상태(local state)처럼 간단한 get/set 인터페이스로 사용할 수 있또록 boilerpalte-free api를 제공한다(필요한 경우 reducers 등으로 캡슐화할 수도 있다.)
  • 우리는 동시성 모드(Concureent mode)를 비롯한 다른 새로운 React의 기능들과의 호환 가능성도 갖는다.
  • 상태 정의는 증분 및 분산되므로 코드 분할이 가능하다.
  • 상태를 사용하는 컴포넌트를 수정하지 않고도 파생된 데이터는 동기식과 비동기식 간의 이동할 수 있다.
  • 우리는 탐색을 일급 개념으로 취급할 수 있고 심지어 링크에서 상태 전환을 인코딩할 수 있다.
  • 역효환성 방식으로 전체 애플리케이션 상태를 유지하는 것은 쉬우므로, 유지된 상태는 애플리케이션 변경에도 살아남을 수 있다.

Recoil 설치

	npm install recoil

Recoil 사용하기

우선 cra에 있는 App.js에서 RecoliRoot를 감싸주어야함

import { RecoilRoot } from 'recoil';
function App() {
  return (
    <div className="App">
      <RecoilRoot>
        
      </RecoilRoot>
    </div>
  );
}

export default App;

아톰 선언하기

store.js

//atom 선언

import { atom } from 'recoil';

export const fontSizeState = atom({
  key: "fontSzieState",
  default: 14,
});

button 만들어보기

FontButton.jsx

import React from 'react'
import { useRecoilState } from 'recoil'
import { fontSizeState } from './store'

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

Text 만들어보기

Text.jsx

import React from 'react'
import { useRecoilState } from 'recoil'
import { fontSizeState } from './store'

export default function Text() {
    const [fontSize,setFontSize] = useRecoilState(fontSizeState);
  return (
    <p style={{fontSize}}>This text will increase in size too.</p>
  )
}

App.js에서 button,font 가져오기

import { RecoilRoot } from "recoil";
import FontButton from "./components/RecoilExeample/FontButton";
import Text from "./components/RecoilExeample/Text";
function App() {
  return (
    <div className="App">
      <RecoilRoot>
        <FontButton />
        <Text />
      </RecoilRoot>
    </div>
  );
}

export default App;

클릭 시 버튼 사이즈와 text 사이즈가 커지게 된다.
RecoilRoot로 인해 provider 처럼 최상위 루트에서 자식들끼리 다른 component임에도 불구하고 똑같이 상태 변하는 것을 알 수 있다.

selector

store.js

import { atom, selector } from "recoil";

export const fontSizeState = atom({
  key: "fontSzieState",
  default: 14,
});

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

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

FontButton.jsx

import React from 'react'
import { useRecoilState } from 'recoil'
import { fontSizeLabelState, fontSizeState } from './store'

export default function FontButton() {
    const [fontSize,setFontSize] = useRecoilState(fontSizeState);
    const [fontSizeLabel,setfontSizeLabel] = useRecoilState(fontSizeLabelState);

  return (
    <>
    <div> Current font size:{fontSizeLabel}</div>
    <button onClick={() => setFontSize((size)=> size+1)} style={{fontSize}}>        
        Click to Enlarge
    </button>
    </>
  )
}

selector는 atom이나 다른 값들이 바뀌면 자기도 같이 바뀌는데 무엇인가 더하거나 그 값을 변경을 취해서 보여준다.

CounterStore.js

import { atom, selector } from "recoil";

export const textState = atom({
  key: "textState", //이 아이디는 유니크해야함
  default: "",
});

export const charCountState = selector({
  key: "charCountState",
  get: ({ get }) => {
    const text = get(textState);
    return text.length;
  },
});

CharacterCounter.jsx

import React from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import { charCountState, textState } from './CounterStore'

export default 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>
  )
}

function CharacterCount(){
  const count = useRecoilValue(charCountState);

  return <>Character Count:{count}</>
}

useRecoliState는 get/set를 같이 가지고 있지만 useRecoilValue는 값만가져와서 사용한다.


비동기 Fetch(Query)

msw를 이용하여 비동기 api 연결하기

handlers.js

import { rest } from "msw";

export const handlers = [
  rest.put("http://localhost:3000/api/user-name", async (req, res, ctx) => {
    const id = req.url.searchParams.get("id");
    return res(
      ctx.json({
        name: id === "1" ? "The one" : "The others",
      })
    );
  }),
];

CureentUserInfo.jsx

import axios from 'axios';
import React from 'react'
import { atom, selector, useRecoilValue } from 'recoil'
import ErrorBoundary from './ErrorBoundary';

const currentUserIdState = atom({
    key:'CurrentUserID',
    default:1,
})


const tableOfUser=[{name:'park'},{name:'min'}];

// const currentUserNameState = selector({
//     key:'CurrentUserName',
//     get:({get})=>{
//         return tableOfUser[get(cureentUserIdState)].name;
//     },
// })

const currentUserNameQuery = selector({
    key:'CurrentUserName',
    get: async({get})=>{
        const response = await axios.get(
            `/api/user-name?id=${get(currentUserIdState)}`
        )        
        return response.data.name;
    }
})

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

export default function CureentUserInfo(){
    return (
        <React.Suspense fallback={<div>Loading...</div>}>
            <CurrentUser/>
        </React.Suspense>
    )
}

React.Suspense는 currentUserNameQuery의 값으로 내려오게 되면 fallback으로 promise 요청중인 상태라면 그 외의 fallback의 상태를 보여준다. username이라는 값이 결정되기 전에 fallback으로 대신하여 상태를 전달하여 보여줌
errorboundary와 유사하다.


error 처리하기

Errorboundary로 error 처리

ErrorBoundary.jsx

import React from 'react';

export default class ErrorBoundary extends React.Component {
    constructor(props) {
      super(props);
      this.state = { hasError: false };
    }
  
    static getDerivedStateFromError(error) {
      // 다음 렌더링에서 폴백 UI가 보이도록 상태를 업데이트 합니다.
      return { hasError: true };
    }
  
    componentDidCatch(error, errorInfo) {
      // 에러 리포팅 서비스에 에러를 기록할 수도 있습니다.
//      logErrorToMyService(error, errorInfo);
    }
  
    render() {
      if (this.state.hasError) {
        // 폴백 UI를 커스텀하여 렌더링할 수 있습니다.
        return <h1>Something went wrong.</h1>;
      }
  
      return this.props.children;
    }
  }

CureentUserInfo.jsx

import axios from 'axios';
import React from 'react'
import { atom, selector, useRecoilValue } from 'recoil'
import ErrorBoundary from './ErrorBoundary';

const currentUserIdState = atom({
    key:'CurrentUserID',
    default:2,
})


const tableOfUser=[{name:'park'},{name:'min'}];

// const currentUserNameState = selector({
//     key:'CurrentUserName',
//     get:({get})=>{
//         return tableOfUser[get(cureentUserIdState)].name;
//     },
// })

const currentUserNameQuery = selector({
    key:'CurrentUserName',
    get: async({get})=>{
        const response = await axios.get(
            `/api/user-name?id=${get(currentUserIdState)}`
        )        
        return response.data.name;
    }
})

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

export default function CureentUserInfo(){
    return (
    <ErrorBoundary>
        <React.Suspense fallback={<div>Loading...</div>}>
            <CurrentUser/>
        </React.Suspense>
    </ErrorBoundary>
    )
}

error시에는 아래 문구가 나온다.


profile
함께 성장하는 것을 좋아하는 프론트엔드 개발자

0개의 댓글