이번 시간에는 Recoil을 좀더 알아보기 위해서 Recoil 튜토리얼에 있는 TodoList를 참고해서 만들어보겠습니다.
src 밑에 recoil이라는 폴더를 생성해주고 그안에 todo.js를 만들어줍니다. 그리고 우리가 사용할 state를 작성해주면 됩니다.
src/recoil/todo.js
import {atom} from "recoil";
export const todoListState = atom({
key: 'todoListState',
default: [],
});
그리고 todo폴더를 만들어서 그안에 TodoList.js파일을 추가해주세요
src/todo/TodoList.js
import React from "react";
import TodoItemCreator from "./TodoItemCreator";
import TodoItem from "./TodoItem";
import { useRecoilValue } from "recoil";
import { todoListState } from "../recoil/todo";
const TodoList = () => {
const todoList = useRecoilValue(todoListState);
return (
<>
<TodoItemCreator />
{todoList.map((todoItem) => (
<TodoItem key={todoItem.id} item={todoItem} />
))}
</>
)
}
export default TodoList;
useRecoilValue를 이용해 todoListState에서 만든 state의 값만 불러올수 있습니다. 그 값으로 map함수를 이용해서 각각에 Item에 생성해주면 됩니다.
그리고 나서 item들을 추가할 수 있는 TodoItemCreator 컴포넌트를 만들어줍니다.
src/todo/TodoItemCreator.js
import React, {useState} from "react";
import { todoListState } from "../recoil/todo";
import { useSetRecoilState } from "recoil";
const TodoItemCreator = () => {
const [inputValue, setInputValue] = useState('');
const setTodoList = useSetRecoilState(todoListState);
// useSetRecoilState 를 사용해서 set함수만을 가져올 수 있다.
const addItem = () => {
setTodoList((oldTodoList) => {
const id = oldTodoList.length
? oldTodoList[oldTodoList.length - 1].id + 1
: 0; // oldTodoList에 원소가 있다면 그 원소에 있는 id 값에 + 1씩 더해주고 없다면
// 기존 0값을 id로 간주한다.
return [
...oldTodoList,
{
id,
text: inputValue,
isComplate: false,
},
];
});
setInputValue('');
};
const onChange = ({ target: { value }) => {
setInputValue(value);
};
return (
<div>
<input type="text" value={inputValue} onChange={onChange} />
<button onClick={addItem}>Add</button>
</div>
);
};
export default TodoItemCreator;
useSetRecoilState hook을 사용해 set함수만 가져올 수도 있다.
input안에 text를 적은다음 버튼을 누르면 추가되는 todolist를 볼 수 있을 것이다. 물론 아직 TodoList Component를 안만들어서 되지않지만, todoList 컴포넌트를 만들어보자.
import React from "react";
import { useRecoilState } from "recoil";
import { todoListState } from "../recoil/todo";
const TodoItem = ({ item }) => {
const [todoList, setTodoList] = useRecoilState(todoListState);
const editItemText = ({ target: {value} }) =>{
const newList = todoList.map((listItem) =>
listItem.id === item.id ? { ...listItem, text: value } : listItem
); // id가 같은 것은 text를 업데이트 하고 아닌 것은 그대로 넣는 list를 만들어주는 set
setTodoList(newList);
};
const toggleItemCompletion = () => {
const newList = todoList.map((listItem) =>
listItem.id === item.id
? { ...listItem, isComplete: !item.isComplete }
: listItem
);
// id가 같은 것은 isComplete를 업데이트 하고 아닌 것은 그대로 넣는 list를 만들어 set
setTodoList(newList);
};
const deleteItem = () => {
const newList = todoList.filter((listItem) => listItem.id !== item.id);
// id와 다른것들을 filterling해서 set해준다.
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>
);
};
export default TodoItem;
useRecoilState hook을 사용하면 앞에서 만들어본 useRecoilValue, useSetRecoilState를 같이 쓴 효과를 볼 수 있다.
useState를 사용하듯이 value와 setter를 받을 수 있다.
이렇게까지 작성한다면 기본적인 기능을 하는 TodoList가 완성된다.
하지만 atom말고 selector라는 것을 사용해보기 위해 다른 기능도 추가해보려 한다.
selector는 state를 통해 도출된 값이라고 볼 수 있다. 예를 들어 state의 길이를 구하는 함수를 만들어놓고 함수가 필요할 때 마다
state처럼 사용할 수 있는 것이다.
한번 만들어보면서 이해해보자.
src/recoil/todo.js
import {atom, selector} from "recoil";
export const todoListState = atom({
key: 'todoListState',
default: [],
});
export const todoListFilterState = atom({
key: "todoListFilterState",
default:"Show All",
}); // 어떤 값을 filter하는지 정하는 state
export const filteredTodoListState = selector({
key: 'filteredTodoListState',
// get에는 객체 안에 get함수가 들어있는 파라미터를 받는다.
// get을 이용해 state들을 불러올 수 있다. 어떤 기준으로
// filtering할지 state와 todoList state를 받아 기준에 따라 filtering 한다.
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;
}
},
}); // 필터 된 todoList를 반환해주는 selector
export const todoListStatsState = selector({
key: 'todoListStatsState',
get: ({ get }) => {
const todoList = get(filteredTodoListState);
const totalNum = todoList.length;
const totalCompletedNum = todoList.filter((item) => item.isComplete).length;
const totalUnCompletedNum = totalNum - totalCompletedNum;
const percentCompleted = totalNum === 0 ? 0 : totalCompletedNum / totalNum;
return {
totalNum,
totalCompletedNum,
totalUnCompletedNum,
percentCompleted,
};
},
}); // todoList의 상태들을 계산해주는 selector
이런식으로 state들을 가공하는 selector를 만들거나 state를 활용해서 여러가지의 개수, 퍼센트를 구하는 selector를 만들어 사용할 수 있습니다.
그럼 이제 selector들을 활용해보면, filteredTodoListState로 화면이 보이게 하기 위해서 TodoList Component를 좀 바꿔줘야 한다.
src/todo/TodoList.js
import React from "react";
import TodoListStats from "./TodoListStats";
import TodoListFiters from "./TodoListFilters";
import TodoItemCreator from "./TodoItemCreator";
import TodoItem from "./TodoItem";
import { useRecoilValue } from "recoil";
import { filteredTodoListState } from "../recoil/todo";
const TodoList = () => {
const todoList = useRecoilValue(filteredTodoListState);// filter된 state로 보이게함.
return (
<>
<TodoListStats /> // 상태값을 보여줄 컴포넌트
<TodoListFiters /> // 필터할 컴포넌트
<TodoItemCreator />
{todoList.map((todoItem) => (
<TodoItem key={todoItem.id} item={todoItem} />
))}
</>
)
}
export default TodoList;
그러면 TodoListStats와 TodoListFilters에 대한 Component만 만들어주면 끝이납니다.
src/todo/TodoListStats.js
import React from "react";
import { useRecoilValue } from "recoil";
import { todoListStatsState } from "../recoil/todo";
const TodoListStats = () => {
const {
totalNum,
totalCompletedNum,
totalUnCompletedNum,
percentCompleted,
} = useRecoilValue(todoListStatsState);
let formattedPercentCompleted = Math.round(percentCompleted * 100);
return (
<ul>
<li>전체 개수: {totalNum}</li>
<li>입력완료된 개수: {totalCompletedNum}</li>
<li>제외된 개수: {totalUnCompletedNum}</li>
<li>퍼센트를 포함한 개수: {formattedPercentCompleted}</li>
</ul>
);
};
export default TodoListStats;
src/todo/TodoListStats.js
import React from "react";
import { useRecoilState } from "recoil";
import { todoListFilterState } from "../recoil/todo";
const 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">Upcompleted</option>
</select>
</>
);
};
export default TodoListFilters;
필요에 따라 useRecoil또는 useRecoilValue를 사용해 불러와서 사용한다. 그러면 이렇게 잘작동되는 것을 볼수 있다. 하지만 여기에 문제가 하나 있는데 아직 Recoil이 나온디 얼마되지 않아서 발생하는 selector에 대한 오류가 있다.
대충 번역하면 index.js에해 구성요소가 제어되지않는 입력을 제어하도록 변경하고 있다는데. 정의되지않는 상태에 정의된 값으로 변경되기 때문에 발생하는 문제라고 번역된다.
이문제에 대해서는
공식사이트에 직접 적혀있으므로 참고해서 고치면 될것같다.
참고
코드
'프론트 엔드 > React' 카테고리의 다른 글
React | Axios란? (feat. Fetch API) (0) | 2021.08.30 |
---|---|
오직 React만을 위한 상태관리라이브러리 - Recoil (0) | 2021.08.30 |
[REACT 개발 필수]CRA(create-react-app)에 ESLint, Prettier 적용, 설정하는법 (0) | 2021.08.29 |