Constate, Recoil

Front-end/React 이론 스터디

Constate, Recoil

조커린 2021. 6. 11. 00:22

 

 

 

 

 

 

 

Constate

Context를 사용하고 싶은데 다루어야 하는 상태가 많아진 상황에서 성능도 챙기고 개발도 편리하게 하고 싶을 때


Context는 리덕스보다 쓰기 쉽다는 장점이 있지만 Context 상태 중 의존하지 않는 다른 값이 바뀌게 될 때에도 컴포넌트가 렌더링 된다는 단점이 있었음
그렇기 떄문에 ContextAPI를 이용할 때 Context 를 구독하는 값마다 적절히 쪼개줘야하는 작업이 필요했다.

constate 함수에 넣는 selector들을 기반으로 Context들이 자동으로 만들어지고 Context를 사용하는 Hook도 자동으로 만들어진다.

useState, useReducer를 그냥 사용하면 되기 때문에 추가적으로 배울 것이 없다.

사용법

  1. npm install constate
  2. constate import후에
    const [Provider, selector1이름, selector2이름...] = constate(()⇒ provider, value들의 셀렉터들...)
  3. value와 리턴해주는 함수를 첫 번째 파라미터로 ( Provider ), value를 셀렉해 파라미터로 넣어줄 수도 있다 (selector hook)

예시

import React, { useState } from "react";
import constate from "constate";

// 1️⃣ Create a custom hook as usual
const useCounter = () => {
  const [count, setCount] = useState(0);
  const increment = () => setCount((prevCount) => prevCount + 1);
  return { count, increment };
};
//1번
const [CounterProvider, useCounterContext] = constate(useCounter);
//2번
const [CounterProvider, useCount, useIncrement] = useState(
  useCounter,
  (value) => value.count,
  (value) => value.increment
);

/**
 * 파라미터로는 useValue과 selector함수가 들어간다.     
 *
 * useValue : value와 value의 setter-> ex) count, increment
 * 뒤에 value로 만들어준 값의 셀렉터(hook)를 구체적으로 구조분해 할당을 통해 가져옴
 *
 */

const Button = () => {
  const { increment } = useCounterContext();
  return <button onClick={increment}>+</button>;
};

const Count = () => {
  // 4️⃣ Use context in other components
  const { count } = useCounterContext();
  return <span>{count}</span>;
};

export default () => {
  // 5️⃣ Wrap your components with Provider
  return (
    <CounterProvider>
      <Count />
      <Button />
    </CounterProvider>
  );
};

Recoil

Context 만을 사용하여 글로벌 상태 관리를 하는 것은 오래전부터 성능이슈가 컸다.

  • 상태 업데이트를 하기 위해서는 공통 조상 컴포넌트로 상태를 끌어올려야 하는데, 이 과정에서 너무 큰 트리가 리렌더링 될 수 있다.(구독하고 있는 친구들이 불필요한 값 변경시에도 렌더링 되는 이슈)
  • Context 를 사용할 때 Consumer는 다양하게 사용되는데 Context에는 하나의 값만 담을 수 있다.
  • 상태가 만들어지는 곳과 사용되는 곳의 코드 분리가 어려워짐

이를 위해서 많은 노력으로 Redux나 Mobx가 생겼다.


하지만 이들을 사용하려면 추가적인 기능을 위해 Redux-saga와 같은 라이브러리를 추가해 주어야 한다.


그리고 React 자체 라이브러리가 아니기에 상태 저장소가 외부에서 처리되며, 이때문에 React내부의 스케쥴러에 대한 접근이 불가하다고 한다.


Redux나 MobX같은 라이브러리들 보다 좀 더 가볍고 리액트 스럽게 사용할 수 있게 페이스북에서 공개한 라이브러리 이다.
단순한 getter, setter를 이용할 수 있으며, 코트 스플리팅 등을 이용할 수 있다는 점에서 장점이 크다.

Recoil의 용어들

  • RecoilRoot : 상태를 사용하는 컴포넌트를 감싸줘야할 컴포넌트. 말 그대로 루트
  • atom : 상태의 일부 상태, 값들을 의미
  • useRecoilValue() : 구독하고 있는 상태값 가져오기 파라미터로 key넣어주기
  • useRecoilState() : atom을 읽고 쓰게 할 수 있다. (getter, setter)
  • useSetRecoilState() : atom의 setter만 가져다 쓰기
  • useResetRecoilState: 값을 기본값으로 reset 시키는 함수
  • selector : derived state의 변경사항을 나타낸다. (derived state : 주어진 state값을 주어진 function으로 수행한 결과값)

이 밖에도 많은 API들이 많다.

Atom (구독할 값 설정)

const todoListState = atom({
  key: "todoListState",
  default: [],
});

key 값으로 어떻게 이 값을 설정할 지에 대해 할당해줄 수 있다.
default에는 initial Value

Atom을 변경해보기

  • useRecoilState를 이용여기서 좀 더 확장하여 이런 함수를 만들어 줄 수도 있다.
  • function TextInput() { const [text, setText] = useRecoilState(textState);/ 앞에서 textState로 만들어준 값 // 전역 상태를 useState처럼 만들어 준다. const onChange = (event) => { setText(event.target.value); }; return ( <div> <input type="text" value={text} onChange={onChange} /> <br /> Echo: {text} </div> ); }
  • const [text, setText] = useRecoilState(textState);
  • useSetRecoilState
    setter만 뽑아냄. 하단의 예제처럼 쓰임
    기존의 값을 기반으로 새로운 값을 만들 수 있도록 updater형식을 사용해야 함.
const setTodoList = useSetRecoilState(todoListState);

const addItem = () => {
  setTodoList((oldTodoList) => [
    ...oldTodoList,
    {
      id: getId(),
      text: inputValue,
      isComplete: false,
    },
  ]);
  setInputValue("");
};

Atom값을 가져와보기 (getter)

useRecoilValue() 을 이용하여 구독중인 값을 가지고 올 수 있다. 파라미터로 atom의 key값을 받는다.

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

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

selector

다른 atom에 의존하는 동적인 데이터를 만들 수 있게 해준다.

  • key : atom을 식별하는 데에 사용되는 고유한 문자열 전역적으로 고유해야 함
  • get : 파생된 상태의 값을 가지고 옴
    다른 atom이나 selector를 리턴할 수 있다.
const certainState = selector ({
    key : "해당 함수의 리턴 값의 키값"
    get : ({get}) => {
        const statename = get(가져오고 싶은 state키)
        // ====== 내부적으로 get으로 가져온 값의 의존하고 있으며 둘 중 하나라도 변하면 값이 재실행된다.
        return 계산된 결과값
    }
});

예시

ToDoList를 필터링 하기 위해서 atom에 저장될 수 있는 필터의 기준을 만들어줄 것 인데 이 기준을 todoListFilterState라고 명명

const todoListFilterState = atom({
  key: "todoListFilterState",
  default: "Show All",
});

기준이 되는 값의 변경이 일어날 경우 필터링된 리스트를 만들어주는 selector를 만들어 줍니다.
하단의 함수에서 filteredToDoListState는 todoListFilterState와 todoListState의 변경을 감지한다.

const filteredTodoListState = selector({
  key: '**filteredTodoListState'**, // 요 이름으로 받아줌
  get: ({get}) => {
    const filter = get(todoListFilterState); // 기준이 될 값을 만들어 준다.
    const list = get(todoListState);

    switch (filter) { // 해당값의 변경에 맞추어 기존 todoList값을 변경해줌
      case 'Show Completed':
        return list.filter((item) => item.isComplete);
      case 'Show Uncompleted':
        return list.filter((item) => !item.isComplete);
      default:
        return list;
    }
  },
});

해당하는 filteredToDoListState의 값의 변경은 이런 식으로 할 수 있다.

function TodoListFilters() {
  // todoListFilterState를 변경할 수 있도록 useRecoilState를 만들어 준다.
  const [filter, setFilter] = useRecoilState(todoListFilterState);

  const updateFilter = ({ target: { value } }) => {
    setFilter(value);
  };

  return (
    <>
      Filter:
      <select value={filter} onChange={updateFilter}>
        <option value="Show All">All</option>
        <option value="Show Completed">Completed</option>
        <option value="Show Uncompleted">Uncompleted</option>
      </select>
    </>
  );
}

또한 Setter도 넣을 수 있다. set으로 구독하는값을 세팅해주는데, useRecoilState로 값을 가져올 때 setState값으로 가져올 수 있다.

비동기 함수의 지원

데이터를 읽어오는 것 같은 비동기 함수도 지원한다.
많은 옵션이 있다...

관련 링크 : 비동기 데이터 쿼리 | Recoil

예를 들면, selector 함수에 특정 값의 변경사항이 있을 시 데이터를 받아오는 쿼리문을 작성할 수 있다.

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

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

비동기 함수에서 에러처리

React.Suspense를 이용하여 에러 발생시에 잡아줄 수 있다.

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

비동기 함수요청시 매개변수가 있는 쿼리

이럴 땐 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));
}

이 밖에도 많은 함수를 지원하지만, 기본 문법 만으로도 구현시 크게 어렵지 않아보이는 게 큰 장점인 듯 합니다.

'Front-end > React 이론 스터디' 카테고리의 다른 글

Redux Middleware  (0) 2021.06.17
API Fetching을 Context와 함께 써보자.  (0) 2021.06.04
CSS Module  (0) 2021.05.31
Context API  (0) 2021.05.29
useCallback  (0) 2021.05.26