Recoil 사용해보기

Lee·2022년 6월 13일
1

사용해보기

목록 보기
1/3
post-thumbnail

기존에 Redux, Redux-toolkit, Redux-saga를 이용해서 상태 관리를 했었는데 코드가 너무 길어져서 다른 상태관리법을 찾다가 Recoil 알게됐다. Recoil을 이용해서 간단한 todolist를 만들어보자

설치

npm install recoil

RecoilRoot

기존에 Redux에서와 비슷하게 감싸지만 안에 따로 store은 넣지 않는다.

//Redux version
 <Provider store={store}>
      <App />
    </Provider>
//Recoil version
    <RecoilRoot>
        <App />
    </RecoilRoot>

Atoms

interface todoListArrayAtomInterface {
  todoValue: string;
  id: number;
  checked: boolean;
}

export const todoListArrayAtom = atom<todoListArrayAtomInterface[]>({
  key: "todoListArrayAtom",
  default: [],
});

Atoms는 상태의 단위이며, 업데이트와 구독이 가능하다. atom이 업데이트되면 각각의 구독된 컴포넌트는 새로운 값을 반영하여 다시 렌더링 된다. atoms는 런타임에서 생성될 수도 있다. Atoms는 React의 로컬 컴포넌트의 상태 대신 사용할 수 있다. 동일한 atom이 여러 컴포넌트에서 사용되는 경우 모든 컴포넌트는 상태를 공유한다.(Recoil본문...)

key : 고유의 키값이다. 다른 atom, selector와 같으면 안된다.
default: 상태의 기본 값이다.

selector

export const checkTodoItem = selector({
  key: "checkTodoItem",
  get: ({ get }) => {
    const todoListArr = get(todoListArrayAtom);
    const unresolvedTodoLen = todoListArr.filter(
      (todoItem) => todoItem.checked === false
    ).length;
    return `남은 할것들 ${unresolvedTodoLen}`;
  },
});

Selector는 atom 또는 selector의 값을 가져와 가공할 수 있다. atom 또는 selector가 업데이트되면 하위의 selector 함수도 다시 실행된다. 컴포넌트들은 selector를 atom처럼 구독할 수 있으며 selectors가 변경되면 컴포넌트들도 다시 렌더링된다.

key : 고유의 키값이다. 다른 atom, selector와 같으면 안된다.
get : get으로 다른 atom, selector를 가져올 수 있다. return값으로 useRecoilValue에서 읽을 수 있는 값을 내보낸다.
set ?: set을 사용하면 쓰기 가능한 상태로 바뀐다. 다른 atom, selector의 상태를 변화시킬 수 있다.

recoil사이트 를 들어가보면 "Recoil은 React처럼 작동하고 생각합니다."라고 적혀 있다. useRecoilState, useRecoilValue, useSetRecoilState들이 리액트의 useState와 매우 유사하게 생겼고 사용법도 유사하다.

useRecoilState

  const [todoListArray, setTodoListArray] = useRecoilState(todoListArrayAtom);

useState처럼 배열을 반환하고 첫번째 값으로 상태값, 두번째 값으로 상태의 값을 바꿀 수 있는 함수가 나온다.

useRecoilValue

  const todoListArray = useRecoilValue(todoListArrayAtom);

useRecoilState에서 return되는 배열중에 첫번째 값만 가져온다. 읽기전용일때 사용된다.

useSetRecoilState

  const setTodoListArray = useSetRecoilState(todoListArrayAtom);

useRecoilState에서 return되는 배열중에 두번째 값만 가져온다.

Recoil을 사용하여 to do list 만들어보기

store.ts

import { atom, selector } from "recoil";

interface todoListArrayAtomInterface {
  todoValue: string;
  id: number;
  checked: boolean;
}

export const todoListArrayAtom = atom<todoListArrayAtomInterface[] | []>({
  key: "todoListArrayAtom",
  default: [],
});

export const checkTodoItem = selector({
  key: "checkTodoItem",
  get: ({ get }) => {
    const todoListArr = get(todoListArrayAtom);
    const unresolvedTodoLen = todoListArr.filter(
      (todoItem) => todoItem.checked === false
    ).length;
    return `남은 할것들 ${unresolvedTodoLen}`;
  },
});

우선 todoListArrayAtom라는 to do list의 내용들을 배열 형태로 저장시키는 atom을 만든다. 그리고 checkTodoItem로 남은 todo가 몇개인지 알려주는 selector를 하나 만들어준다.

InputAndButton.tsx

import { useState, ChangeEvent, FormEvent } from "react";
import { useSetRecoilState } from "recoil";

import { todoListArrayAtom } from "./store";

export function InputAndButton() {
  const [inputValue, setInputValue] = useState("");
  const setTodoListArray = useSetRecoilState(todoListArrayAtom);

  const handleOnChange = (e: ChangeEvent<HTMLInputElement>) => {
    setInputValue(e.target.value);
  };
  const handleOnSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setTodoListArray((prevTodoListArray) => [
      ...prevTodoListArray,
      { todoValue: inputValue, id: getId(), checked: false },
    ]);
    setInputValue("");
  };
  return (
    <form onSubmit={handleOnSubmit}>
      <input value={inputValue} onChange={handleOnChange} />
      <button>추가</button>
    </form>
  );
}
let todoItemid = 0;
const getId = () => {
  return todoItemid++;
};

TodoListDisPlay.tsx

import { MouseEvent } from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import styled from "styled-components";

import { checkTodoItem } from "./store";
import { todoListArrayAtom } from "./store";

export function TodoListDisPlay() {
  const [todoListArray, setTodoListArray] = useRecoilState(todoListArrayAtom);
  const checkTodo = useRecoilValue(checkTodoItem);

  const handleDeleteOnClick = (e: MouseEvent<HTMLButtonElement>) => {
    const target = e.target;
    if (target instanceof Element) {
      setTodoListArray((todoListArr) =>
        todoListArr.filter((todoItem) => todoItem.id !== parseInt(target.id))
      );
    }
  };
  const handleCheckOnClick = (e: MouseEvent<HTMLButtonElement>) => {
    const target = e.target;
    if (target instanceof Element) {
      setTodoListArray((todoListArr) =>
        todoListArr.map((todoItem) =>
          todoItem.id !== parseInt(target.id)
            ? todoItem
            : { ...todoItem, checked: !todoItem.checked }
        )
      );
    }
  };

  return (
    <>
      <div>{checkTodo}</div>
      {todoListArray.map((todoListItem) => (
        <div key={todoListItem.id}>
          {todoListItem.checked ? (
            <CheckedSpan
              onClick={handleCheckOnClick}
              id={String(todoListItem.id)}
            >
              {todoListItem.todoValue}
            </CheckedSpan>
          ) : (
            <span onClick={handleCheckOnClick} id={String(todoListItem.id)}>
              {todoListItem.todoValue}
            </span>
          )}
          <button id={String(todoListItem.id)} onClick={handleDeleteOnClick}>
            삭제
          </button>
        </div>
      ))}
    </>
  );
}

const CheckedSpan = styled.span`
  text-decoration: line-through;
`;

결과

(잘못된 정보나 피드백 해주실거 알려주시면 바로 반영해서 수정하겠습니다. 읽어주셔서 감사합니다)

profile
프론트엔드 개발자

0개의 댓글