본문 바로가기

내용 복습/Next.js

[React] 상태관리 라이브러리 Recoil 연습

지난 next.js로 프로젝트를 하면서 프롭스 드릴링이 4번 정도 일어나는 경험을 했기에 상태관리 라이브러리의 필요성은 느끼고 있었다. 아무래도 드릴링 뎁스가 길어지면 코드는 비요율적일 수밖에 없고 사이드 이펙트로 많을 것이기에. 일단 사용하기 편한 recoil을 공부해봤다. 아래 내용은 recoil 공식문서의 todolist 만들기이다.

 

npm install recoil  /  yarn add recoil

 

패키지 메니저에 알맞게 설치하고 루트 컴포넌트인 index.js의 본문을 <RecoilRoot>로 감싸준다. recoil을 사용하기 위해선 두가지만 기억하면 되는데, atom과 selector이다.

Atom 상태(state)의 일부를 나타낸다. Atoms는 어떤 컴포넌트에서나 읽고 쓸 수 있다. atom의 값을 읽는 컴포넌트들은 암묵적으로 atom을 구독한다. 그래서 atom에 어떤 변화가 있으면 그 atom을 구독하는 모든 컴포넌트가 재 렌더링 되는 결과가 발생할 것이다.

Selector는 파생된 상태(derived state)의 일부를 나타낸다. 파생된 상태는 상태의 변화다. 파생된 상태를 어떤 방법으로든 주어진 상태를 수정하는 순수 함수에 전달된 상태의 결과물로 생각할 수 있다.

 

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

 

우리는 atom에 고유한 key를 주고 비어있는 배열 값을 default로 설정할 것이다. 이 atom의 항목을 읽기 위해, 우리는 useRecoilValue() 훅을 우리의 TodoList 컴포넌트에서 사용할 수 있다.

 

function TodoList() {
  // changed from todoListState to filteredTodoListState
  const todoList = useRecoilValue(filteredTodoListState);

  return (
    <>
      <TodoListStats />
      <TodoListFilters />
      <TodoItemCreator />

      {todoList.map((todoItem) => (
        <TodoItem key={todoItem.id} item={todoItem} />
      ))}
    </>
  );
}

 

 

새로운 todo 아이템을 생성하기 위해 우리는 todoListState 내용을 업데이트하는 setter 함수에 접근해야 한다. 우리는 TodoItemCreator 컴포넌트의 setter 함수를 얻기 위해 useSetRecoilState() 훅을 사용할 수 있다.

 

function TodoItemCreator() {
  const [inputValue, setInputValue] = useState('');
  const setTodoList = useSetRecoilState(todoListState);

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

  const onChange = ({target: {value}}) => {
    setInputValue(value);
  };

  return (
    <div>
      <input type="text" value={inputValue} onChange={onChange} />
      <button onClick={addItem}>Add</button>
    </div>
  );
}

// 고유한 Id 생성을 위한 유틸리티
let id = 0;
function getId() {
  return id++;
}

 

 

TodoItem 컴포넌트는 todo 리스트의 값을 표시하는 동시에 텍스트를 변경하고 항목을 삭제할 수 있다. 우리는 todoListState를 읽고 항목 텍스트를 업데이트하고, 완료된 것으로 표시하고, 삭제하는 데 사용하는 setter 함수를 얻기 위해 useRecoilState()를 사용한다.

 

function TodoItem({item}) {
  const [todoList, setTodoList] = useRecoilState(todoListState);
  const index = todoList.findIndex((listItem) => listItem === item);

  const editItemText = ({target: {value}}) => {
    const newList = replaceItemAtIndex(todoList, index, {
      ...item,
      text: value,
    });

    setTodoList(newList);
  };

  const toggleItemCompletion = () => {
    const newList = replaceItemAtIndex(todoList, index, {
      ...item,
      isComplete: !item.isComplete,
    });

    setTodoList(newList);
  };

  const deleteItem = () => {
    const newList = removeItemAtIndex(todoList, index);

    setTodoList(newList);
  };

  return (
    <div>
      <input type="text" value={item.text} onChange={editItemText} />
      <input
        type="checkbox"
        checked={item.isComplete}
        onChange={toggleItemCompletion}
      />
      <button onClick={deleteItem}>X</button>
    </div>
  );
}

function replaceItemAtIndex(arr, index, newValue) {
  return [...arr.slice(0, index), newValue, ...arr.slice(index + 1)];
}

function removeItemAtIndex(arr, index) {
  return [...arr.slice(0, index), ...arr.slice(index + 1)];
}

 

 

Selector는 파생된 상태(derived state)의 일부를 나타낸다. 파생된 상태를 어떤 방법으로든 주어진 상태를 수정하는 순수 함수에 전달된 상태의 결과물로 생각할 수 있다. 파생된 상태는 다른 데이터에 의존하는 동적인 데이터를 만들 수 있다.

 

필터링 된 todo 리스트를 구현하기 위해서 우리는 atom에 저장될 수 있는 필터 기준을 선택해야 한다. 우리가 사용하게 될 필터 옵션은 "Show All", "Show Completed"과 "Show Uncompleted"가 있다. 기본값은 "Show All"이 될 것이다.

 

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

 

todoListFilterState todoListState를 사용해서 우리는 필터링 된 리스트를 파생하는 filteredTodoListState selector를 구성할 수 있다

UI는 todoListFilterState의 기본값인 "Show All"과 동일하다. 필터를 변경하려면 우리는 TodoListFilter 컴포넌트를 구현해야 한다.

const filteredTodoListState = selector({
  key: 'filteredTodoListState',
  get: ({get}) => {
    const filter = get(todoListFilterState);
    const list = get(todoListState);

    switch (filter) {
      case 'Show Completed':
        return list.filter((item) => item.isComplete);
      case 'Show Uncompleted':
        return list.filter((item) => !item.isComplete);
      default:
        return list;
    }
  },
});

UI는 todoListFilterState의 기본값인 "Show All"과 동일하다. 필터를 변경하려면 우리는 TodoListFilter 컴포넌트를 구현해야 한다.

function TodoListFilters() {
  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>
    </>
  );
}

 

 

사용하면서 어려웠던 점은 컴포넌트를 나누면서 임포트를 하는 문제가 가장 많은 에러를 발생시켰다. select의 개념이 잘 이해되지 않았던 점도 있지만 임포트할 때 중괄호 {}를 사용하여 이름을 감싸줘야 똑바로 작동하는 점이 신기했다. 아직까지 구조이해가 더 필요한 것 같지만, 프로젝트에 적용하면서 더 효율적으로 코드 작성을 할 수 있다는 점에서 꼭 도입해야 겠다는 생각을 했다.

'내용 복습 > Next.js' 카테고리의 다른 글

create-react-app 대신 vite로 리액트 프로젝트를 생성하는 이유  (0) 2024.08.10
[React] cookie 적용 시간 처리  (0) 2024.04.22
[React] state(2)  (1) 2024.03.28
[React] State 정리(1)  (1) 2024.03.27
[React] Props(2)  (1) 2024.03.14