230103 항해99 58일차 Recoil

요니링 컴터 공부즁·2023년 1월 11일
0
post-custom-banner

왜 Redux 대신 Recoil을 쓸까?

  1. Redux의 복잡한 코드
  2. 간단한 Recoil 의 개념
  3. 쉽게 사용하는 비동기 로직

Recoil atom 사용법

  1. index.tsx (혹은 index.jsx)에서 렌더링 하고 있는 root를 RecilRoot를 통해서 감싸준다. Redux에서는 하나의 store를 연결해주는 과정이지만, Recoil에서는 atom들이 떠다니는 Root를 설정해준다고 추상화 할 수 있다.
import * as ReactDOMClient from "react-dom/client";
import { RecoilRoot } from "recoil";

import App from "./App";

const rootElement = document.getElementById("root");
const root = ReactDOMClient.createRoot(rootElement);

root.render(
  <RecoilRoot>
    <App />
  </RecoilRoot>
)
;
  1. 전역적으로 사용하길 원하는 state를 atom이라는 비눗방울로 띄어서 어디서든지 사용할 수 있도록 만든다.
    key에 atom을 구분해줄 고유의 값을 입력하고, default에는 해당 key값을 가진 atom의 기본값으로 설정해줄 value를 넣어준다.
import { atom } from "recoil"

export const user = atom({
  key: "user",
  default: {
    id: "Admin",
    pwd: "Admin",
  },
});

export const counting = atom({
  key: "counting",
  default: 0,

});
  1. useState처럼 사용한다.
import { useRecoilState } from "recoil";
import { counting } from "./store";

export function Example() {
  const [count, setCount] = useRecoilState(counting);
  const handleIncrease = () => {
    setCount((prev) => prev + 1);
  }
  return (
    <div>
      <span>{count}</span>
      <button onClick={handleIncrease}>increase</button>
    </div>
  );

}

타입스크립트를 적용한 예제

//atom.ts

import { atom } from "recoil";

export interface IUser {
  id: string;
  pwd: string;
  name: string;
}

export const user = atom<IUser>({
  key: "user",
  default: {
    id: "admin",
    pwd: "admin",
    name: "관리자"
  }
});
// App.tsx
import { useRecoilState } from "recoil";
import { IUser, user } from "./atom";

export default function App() {
  const [LoginUser, setLoginUser] = useRecoilState<IUser>(user);
  return (
    <div>
      <p>userName: {LoginUser.name}</p>
      <p>userId: {LoginUser.id}</p>
      <p>userPwd: {LoginUser.pwd}</p>
    </div>
  );
}

Recoil selector

selector을 활용해 비동기 통신을 했을 때 체감되는 강력한 기능은 캐싱 이다. selector을 활용해 비동기 통신을 해온다면, 내부적으로 알아서 값을 캐싱 해주기 때문에 이미 한번 비동기 통신을 하여 값이 캐싱되어 있다면 매번 같은 비동기 통신을 하지 않고 캐싱 된 값을 추적해 사용한다.


Recoil selector 사용법

  1. selector의 get 활용하기
// atom.ts

import { atom, selector } from "recoil";

export type status = "DONE" | "DOING";

interface toDo {
  status: status;
  contents: string;
}

export const selectStatus = atom<status>({
  key: "nowStatus",
  default: "DOING"
});

export const toDos = atom<toDo[]>({
  key: "toDos",
  default: [
    { status: "DOING", contents: "default 1" },
    { status: "DONE", contents: "default 2" },
    { status: "DONE", contents: "default 3" },
    { status: "DOING", contents: "default 4" },
    { status: "DOING", contents: "default 5" }
  ]
});

export const selectToDo = selector<toDo[]>({
  key: "selectToDos",
  get: ({ get }) => {
    const originalToDos = get(toDos);
    const nowStatus = get(selectStatus);
    return originalToDos.filter((toDo) => toDo.status === nowStatus);
  }

});
// App.tsx

import React from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import { selectStatus, selectToDo, user } from "./atom";

export default function App() {
  const [status, setStatus] = useRecoilState(selectStatus);
  const selectToDos = useRecoilValue(selectToDo);

  const handleStatus = (event: React.ChangeEvent<HTMLSelectElement>) => {
    setStatus(event.currentTarget.value as any);
  };

  return (
    <>
      <div>
        <select value={status} onChange={handleStatus}>
          <option value="DOING">DOING</option>
          <option value="DONE">DONE</option>
        </select>
        <ul>
          {selectToDos.map((toDo, index) => {
            return (
              <li key={index}>
                <span>status: {toDo.status}</span>
                <br />
                <span>content: {toDo.contents}</span>
              </li>
            );
          })}
        </ul>
      </div>
    </>
  );

}
  1. selector의 set 활용하기
// atom.ts

export const selectToDo = selector<toDo[]>({
  key: "selectToDos",
  get: ({ get }) => {
    const originalToDos = get(toDos);
    const nowStatus = get(selectStatus);
    return originalToDos.filter((toDo) => toDo.status === nowStatus);
  },
  set: ({ set }, newToDo) => {
    set(toDos, newToDo);
  }
}
);
import React, { useState } from "react";
import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
import { selectStatus, selectToDo, toDo, toDos } from "./atom";

export default function App() {
  const [status, setStatus] = useRecoilState(selectStatus);
  const selectToDos = useRecoilValue(selectToDo);

  const toDoAtom = useRecoilValue(toDos);

  // 아래가 selector 의 set 을 위해 추가된 코드
  const [contents, setContents] = useState("");
  const setNewToDos = useSetRecoilState(selectToDo);

  const handleStatus = (event: React.ChangeEvent<HTMLSelectElement>) => {
    setStatus(event.currentTarget.value as any);
  };

  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setContents(event.currentTarget.value);
  };

  const handleSubmit = (event: React.ChangeEvent<HTMLFormElement>) => {
    event.preventDefault();
    if (contents === "") {
      return;
    } else {
      const newToDoList: toDo[] = [
        ...toDoAtom,
        {
          contents,
          status: "DOING"
        }
      ];

      setNewToDos(newToDoList);
      setContents("");
    }
  };

  return (
    <>
      <form onSubmit={handleSubmit}>
        <input value={contents} onChange={handleInputChange} />
        <button>Submit</button>
      </form>
      <br />
      <div>
        <select value={status} onChange={handleStatus}>
          <option value="DOING">DOING</option>
          <option value="DONE">DONE</option>
        </select>
        <ul>
          {selectToDos.map((toDo, index) => {
            return (
              <li key={index}>
                <span>status: {toDo.status}</span>
                <br />
                <span>content: {toDo.contents}</span>
              </li>
            );
          })}
        </ul>
      </div>
    </>
  );

}
  1. selector을 활용한 비동기 통신
export const selectId = atom({
  key: "selectId",
  default: 1
});

export const selectingUser = selector({
  key: "selectingUser",
  get: async ({ get }) => {
    const id = get(selectId);
    const user = await fetch(
      `https://jsonplaceholder.typicode.com/users/${id}`
    ).then((res) => res.json());
    return user;
  },
  set: ({ set }, newValue) => {
    set(nowUser as any, newValue);
  }

});
  1. selectorFamily를 활용해 컴포넌트에서 직접 매개변수 넘기기
export const selectUser = selectorFamily({
  key: "selectOne",
  get: (id: number) => async () => {
    const user = fetch(
      `https://jsonplaceholder.typicode.com/users/${id}`
    ).then((res) => res.json());
    return user;
  }
});

// 컴포넌트에서 사용 시 

const user = useRecoilValue<IUser>(selectUser(id));

참조:
Recoil, 리액트의 상태관리 라이브러리

post-custom-banner

0개의 댓글