Recoil 공식문서의 예제를 따라서 간단한 상태관리의 예제를 다뤄보자. 늘 그렇듯이 todoList는 상태관리를 연습하는데 좋은 예제이다. 순서대로 아래에서 살펴보자.
import React from 'react'
import { RecoilRoot } from 'recoil'
import TodoList from './components/todoRecoil/TodoList'
function App() {
return (
<RecoilRoot>
<TodoList/>
</RecoilRoot>
)
}
export default App
recoil를 import 했다면, 이제 RecoilRoot를 최상위 컴포넌트에서 주입해주면 된다. 이를 통해서 전역에서 관리하는 상태관리소를 가볍게 생성한 것이다.
- 출처 : 리코일 공식홈페이지
import { atom } from "recoil";
export const todoListState = atom({
key: 'todoListState',
default: [],
});
공식문서에서는 todoListState 하나를 통해서 todoList를 생성하여 추가하고,생성된 파일을 읽고, 수정하고, 삭제하는 예제를 다룬다.
import React from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import { todoListState } from "./todoStore";
import TodoItem from "./TodoItem";
import TodoItemCreator from "./TodoItemCreator";
function TodoList() {
const todoList = useRecoilValue(todoListState);
return (
<div>
<hr />
<TodoItemCreator />
<hr />
{todoList.map((todoItem) => (
<TodoItem key={todoItem.id} item={todoItem} />
))}
</div>
);
}
export default TodoList;
우선 TodoList 컴포넌트는 TodoItemCreator 컴포넌트와 TodoItem 컴포넌트를 하위컴포넌트로 가진다. TodoItemCreator는 새로운 todo를 생성하는 컴포넌트이고, TodoItem 컴포넌트는 todoListState에 있는 todo의 배열을 map으로 돌려 화면에 그려주는 컴포넌트이다. 뷰에서의 구분을 위해서 필자는 hr 태그를 추가했다.
import React, { useState } from "react";
import { useSetRecoilState } from "recoil";
import { todoListState } from "./todoStore";
export default 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>
<div style={{display:"flex", alignItems:"center", gap:"10px", width:"fit-content", margin:"0 auto"}}>
<label htmlFor="todoCreate">todoCreate</label>
<input id="todoCreate" type="text" value={inputValue} onChange={onChange} />
<button onClick={addItem}>Add</button>
</div>
</div>
);
}
// 고유한 Id 생성을 위한 유틸리티
let id = 1;
function getId() {
console.log(id)
return id++;
}
useSetRecoilState에서 상태를 변경하는 setTodoList 함수를 가져와서 생성을 위한 준비를 하였다. 이런 식으로 하나만 가져올 수도 있다. 만약 value만을 가져와서 사용하기 원한다면 useSetRecoilValue를 통해서 값만을 가져와서 사용할 수도 있다.
해당 컴포넌트의 핵심은 input 태그이다. 그런데 공식문서를 보니 onChange 부분에서 기존에 내가 작성했던 코드에 비해서 더욱 깔끔하고 세련된 작성법(구조분해할당)이 기록되어 있는 것을 볼 수 있다. event 객체를 사용하는 부분에 있어서의 깔끔함이었다.
// 기존의 코드
const onChange = (e) => {
setInputValue(e.target.value);
};
// 공식문서의 코드
const onChange = ({target: {value}}) => {
setInputValue(value);
};
이렇게 작성이 된다면 아래와 같은 작성도 가능하다는 말이 될 것이다.
// 공식문서의 코드 응용
const onChange = ({target: {name, value}}) => {
setInputValue(pre => ({...pre, [name]:value}));
};
이를 위해서는 이에 대한 useState를 배열(useState({ text: '' });)로 만들어주어야 하고 해당 내용에 초기값을 설정해줘야 할 것이다. 그렇지 않으면 controlled component와 uncontrolled component와의 혼용 사용으로 인한 경고문구가 콘솔에 출력될 것이다. 이렇게 함으로 새로운 todo가 생성되어, 상태 안으로 저장될 것이다. 이제는 이것을 읽고, 수정하고, 삭제하는 부분에 대해서 살펴보자.
해당 로직은 map 메서드와 TodoItem 컴포넌트를 통해서 제어된다. TodoList 컴포넌트는 반복문을 통해서 todoListState의 상태관리에 담겨진 배열의 요소를 하나씩 props를 통해서 TodoItem 컴포넌트에 전달한다.
이렇게 전달된 TodoItem 컴포넌트는 이를 에 대한 값을 초기값으로 받아서 input 태그에 주입하여, 이를 즉시적으로 수정하는 로직을 만들었다. 수정은 text에 대한 수정과 check 박스를 통해서 isComplete를 수정하는 작업이다. checked는 해당 값이 true인지, false인지에 따라서 체크를 반영하는 input의 속성이다. 물론 사용자가 클릭을 통해서 onChange를 통해서 값을 변경하여 반영되는 로직을 작성할 수 있는데, 공식문서에서는 이러한 예제를 통해서 해당 로직에 접근했다.
뿐만 아니라, 텍스트가 변경될 때에는 map으로 가져온 해당 요소를 상태에서 찾아서, slice 메서드를 통해서 값을 변경하는 로직도 작성함으로 읽고, 수정하고, 삭제하는 로직을 구현하였다.