TaskMaster 프로젝트 - Recoil 사용해보기

nooori·2024년 6월 21일
post-thumbnail

TaskMaster - 작업 관리 프로젝트를 진행하던 중 localStorage 동기화 관련해서 문제를 겪게 되었다😱

문제

  • 작업 checkbox를 누르면 작업의 status가 '완료'로 변경이 되니까 UI 상에서도 '완료' 카테고리로 넘어가야 하는데 실시간으로 이루어지지 않고 새로고침을 해야지만 UI에 반영이 되었다.
  • 검색을 해보니 데이터가 실시간으로 변경되지 않는 이유는 'React'상태와 'localStorage'가 별개의 독립적인 저장소이기 때문이다. 만약 실시간으로 'localStorage'와의 동기화를 원한다면 직접적인 'localStorage' 업데이트 로직을 추가하는 방법도 있지만 그에 따른 다른 부수적인 사항들도 고려를 해야한다. 무엇보다도 새로고침을 하지 않고도 localStorage의 데이터를 실시간으로 받아오는 것은 아무리 useState와 useEffect을 사용하더라도 한계가 있었다. 그래서 상태 관리 라이브러리인 Recoil을 사용해보았다.

해결 과정

  1. 먼저 Recoil을 다운받았다.
yarn add recoil
  1. 그리고 context를 사용하는 것처럼 recoil을 사용할 컴포넌트들을 RecoilRoot로 감쌌다. Recoil을 사용하기 위해서는 어플리케이션의 루트 컴포넌트를 RecoilRoot로 감싸야 한다. RecoilRoot가 Recoil 상태 관리의 context를 제공하기 때문이다.
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <RecoilRoot>
      <App />
    </RecoilRoot>
  </React.StrictMode>
);
  1. 그런 다음 Recoil의 상태를 정의했다. Recoil 상태를 정의하려면 atom이나 selector를 사용하면 되는데 이번에는 atom을 사용했다. atom은 Recoil의 상태 단위를 나타낸다.
const initialTasks = JSON.parse(localStorage.getItem('tasks')) || [];

initialTasks에 초기값을 정의한다. localStorage에 저장된 tasks 항목을 가져온 다음 JSON 문자열 형태로 저장하는데 이때 localStorage에 tasks 항곰이 없거나 'null'일 경우를 대비해서 빈 배열 [ ]을 기본값으로 사용하도록 한다. 결과적으로 initialTasks는 localStorage에 저장된 작업 목록을 담고 있거나, 저장된 항목이 없는 경우 빈 배열을 담게 된다.

import { atom } from 'recoil';

export const tasksState = atom({
  key: 'tasksState',
  default: initialTasks,
});

key는 tasksState는 atom의 고유한 식별자이다. Recoil 상태는 이 key를 통해 구분되고 이 key는 유일해야한다. 그리고 앞전에 정의해둔 initialTasks를 기본값으로 설정한다. 이 atom은 App에서 전역 상태로 사용된다. tasksState를 사용하면 Recoil의 상태 hook(useRecoilState, useRecoilValue, useSetRecoilState)을 통해 상태를 읽고 업데이트할 수 있다.

  1. 그리고 마지막으로 실시간 데이터 반영이 되어야하는 곳에 recoil을 사용했다. Task 컴포넌트에서
const [tasks, setTasks] = useRecoilState(tasksState);
  • Task 컴포넌트에 'updateLocalStorage' 함수를 만들고, 전달받은 task를 localStorage에 저장하도록 하였다.
const updateLocalStorage = (updateTasks) => {
    localStorage.setItem('tasks', JSON.stringify(updateTasks))
}
  • 작업 추가, 수정, 삭제, 업데이트와 관련된 각 함수에서는 useRecoilState의 setTasks를 통해 상태를 업데이트하고, 동시에 updateLocalStorage 함수를 호출하여 localStorage에도 변경 사항을 저장하였다.
const handleAdd = (task) => {
  const updatedTasks = [...tasks, task];
  setTasks(updatedTasks);
  updateLocalStorage(updatedTasks) 
}
const handleUpdate = (updated) => {
  const updatedTasks = tasks.map((t) => (t.id === updated.id ? updated : t));
  setTasks(updatedTasks);
  updateLocalStorage(updatedTasks)
};
const handleDelete = (deleted) => {
  const updatedTasks = tasks.filter((t) => t.id !== deleted);
  setTasks(updatedTasks);
  updateLocalStorage(updatedTasks);
};
const handleEdit = (edited) => {
  if (edited) {
    const updatedTasks = tasks.map((t) => t.id === edited.id ? edited : t);
    setTasks(updatedTasks);
    updateLocalStorage(updatedTasks);
  }
}
  • useEffect 훅을 사용하여 'tasks'의 상태가 변경될 때마다 'updateLocalStorage' 함수를 호출하여 localStorage의 tasks의 내용도 업데이트했다.
 useEffect(() => {
    updateLocalStorage(tasks)
  }, [tasks]);

참고한 recoil 공식 사이트

https://recoiljs.org/ko/

결과

Recoil을 도입하여 React 애플리케이션에서 localStorage와의 독립성 문제를 효과적으로 해결했다. 이전에는 체크박스를 클릭하여 작업 상태를 변경하거나 다른 작업을 수행할 때마다 UI가 즉시 업데이트되지 않고, 사용자가 직접 페이지를 새로고침해야만 localStorage의 데이터가 반영되는 문제가 있었다.

Recoil을 통해 관리되는 상태인 tasksState atom을 활용하여, 작업의 상태 변경이 발생할 때마다 Recoil 상태가 업데이트되고 변경 사항이 자동으로 UI에 반영되도록 하였다. 예를 들어, 체크박스를 클릭하여 작업의 상태를 변경하면 tasksState가 업데이트되어 UI가 실시간으로 변경된다. 이 과정에서 Recoil은 React 컴포넌트들 사이에서 상태를 관리하고, 변경 사항이 있을 때마다 해당 상태를 자동으로 동기화시킨다.

이렇게 함으로써 사용자의 작업 데이터는 항상 최신 상태로 유지되고, 페이지를 새로고침하지 않아도 일관된 데이터를 사용자에게 제공할 수 있다. 이러한 접근 방식은 사용자 경험을 크게 향상시키며, 데이터의 정확성과 일관성을 유지하는 데 중요한 역할을 한다.

이렇게 구현하니, 작업의 상태 변경이나 다른 작업 수행 시 UI와 localStorage 간의 동기화 문제를 해결할 수 있었고, 사용자는 항상 실시간으로 업데이트된 데이터를 확인할 수 있게 되었다.

느낀 점

이번 프로젝트를 하면서 그동안 알고 있기만 했던 recoil 라이브러리를 처음 사용해봤는데 생각보다 사용법도 매우 간단했다. 사용방법이 직관적이고 쉽게 사용할 수 있었는데 특히, 어플리케이션의 전역 상태를 관리하는데 복잡한 설정이나 코드가 거의 없어서 빠르게 적용할 수 있었다. Recoil의 상태 관리 방식을 통해 localStorage에 저장된 데이터를 손쉽게 불러오고, 상태 변경 시마다 자동으로 localStorage에 업데이트할 수 있었는데 덕분에 데이터를 새로고침을 해도 유지할 수 있었고 코드도 간결해서 매우 만족스러웠다. 이런 recoil의 특성으로 상태 관리와 데이터 저장소 동기화를 효율적으로 처리할 수 있었고, 개발 경험도 향상되었다고 생각한다. 앞으로도 recoil을 적극적으로 활용해볼 계획이다.

0개의 댓글