TODO interface를 만드는데, 명시된 string 만을 가질 수 있다고 설정 해줄 수 있다.
interface IToDo {
text: string;
category: "TODO" | "DOING" | "DONE";
}
CreateToDo, ToDo, ToDoList로 컴포넌트를 분리
key 에러를 해결하기 위해 ToDo에 키 값만 주면 된다.
{toDos.map((toDo) => (
<ToDo key={toDo.id} {...toDo} />
))}
✅ 1. 새 익명함수를 만들어 인자를 전달하는 방법
<button onClick={onClick}>Doing</button>
<button onClick={() => onClick("DOING")}>Doing</button>
첫번째 코드처럼 작성해도 작동은 하지만 인자가 넘겨지지 않으므로 두번째 코드로 익명 함수를 써서 인자를 넘겨주도록 한다.
const onClick = (newCategory: IToDo["category"]) => {
console.log("I wanna to ", newCategory);
};
{category !== "DOING" && (
<button onClick={() => onClick("DOING")}>Doing</button>
)}
✅ 2. 버튼에 각각 name을 지정해주고 인자 없이 함수 쓰기
const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
const {
currentTarget: { name },
} = event;
};
{category !== "DOING" && (
<button name="DOING" onClick={onClick}>
Doing
</button>
)}
newCategory: IToDo["category"]
== newCategory: "TODO" | "DOING" | "DONE"
todo의 category를 수정해보자💫
✅1. 수정하고자 하는 todo의 경로를 알아야한다.
const targetIndex = oldToDos.findIndex((toDo) => toDo.id === id);
console.log(targetIndex);
findIndex
는 조건을 만족하는 todo의 index를 찾아준다.
✅2. 새로운 todo를 만들어서 원래의 todo를 update. 즉 targetIndex에 있는 todo를 newToDo로 바꿔주면 된다. (6.15에서 계속)
["pizza", "mango", "kimchi", "kimbab"] 배열에서 "mango"를 "apple"로 교체해보자.
교체하는 순서는 다음과 같다.
✅1. mango index 구하기
✅2. 배열을 두 부분으로 나누기
const food = ["pizza", "mango", "kimchi", "kimbab"];
const front = ["pizza"];
const back = ["kimchi", "kimbab"];
const finalPart = [...front, "apple", ...back];
front는 "mango" 이전 원소의 배열이고, back은 모든 "mango" 이후 원소의 배열이다. ...
은 배열 안에 있는 모든 원소를 풀어놓는다는 의미인데, ...
를 쓰지 않으면 배열안에 배열을 넣게 된다.
콘솔창에 이 과정을 써보는 연습을 해보자.
setToDos((oldToDos) => {
const targetIndex = oldToDos.findIndex((toDo) => toDo.id === id);
const newToDo = { text, id, category: name };
return [
...oldToDos.slice(0, targetIndex),
newToDo,
...oldToDos.slice(targetIndex + 1),
];
});
완성된 코드는 다음과 같고, 이렇게 작성하면 oldToDos에 오류가 뜨는데 category라는 prop의 타입이 호환되지 않기 때문이다. category는 "TODO", "DOING", "DONE" 중 하나여야 하는데 newToDo의 category는 그냥 string 여서 문제가 나는 것🤣
이 문제를 회피 하기 위해 타입스크립트에게 체크하지 말라고 as any
를 쓰면 되는데 별로 좋은 방법은 아니여서 6.13 의 1번 방법을 쓰는 게 더 나을 듯 하다.
const newToDo = { text, id, category: name as any };
따라서 원래의 toDo를 업데이트 하는 것이 아니라, 원래 있던 toDo를 지우고 새 배열을 만드는 것!
지금까지는 toDoState에 "DOING", "TODO", "DONE"을 구별하지 않고 모든 todo를 담고있다. selector
를 이용해서 이 todo들을 구별해보자.
정리하자면, selector는 atom의 output을 변형시키는 도구이다.
selector는 key와 get function이 필요한데, get function은 인자로 객체를 받고, 그 객체에는 get function이 들어가 있는데 이것을 이용하면 selector의 내부로 atom을 가지고 올 수 있다.
export const toDoSelector = selector({
key: "toDoSelector",
get: ({ get }) => {
const toDos = get(toDoState);
return [
toDos.filter((toDo) => toDo.category === "TODO"),
toDos.filter((toDo) => toDo.category === "DOING"),
toDos.filter((toDo) => toDo.category === "DONE"),
];
},
});
6.15에서는 한 곳에 데이터를 몰아놓고 컴포넌트 안에서 수정하는 대신, atom에 데이터를 모아두고 selector로 데이터를 변형하여 코드를 작성해 보았다. 6.16에서는 다른 방식을 써보기로 하자
✅1. 사용자가 현재 선택한 카테고리를 저장하는 state를 만들기
const onInput = (event: React.FormEvent<HTMLSelectElement>) => {
console.log(event.currentTarget.value);
};
✅2. value를 category state atom과 연결
이제 ToDoList.tsx에서 이차원배열이 아닌 일차원배열 하나만 가져오게 된다. 모든 처리는 selector에서 할 것인데, category에 따라서 selector가 각각의 toDo 배열을 반환한다.
//ToDoList.tsx
const toDos = useRecoilValue(toDoSelector);
{toDos?.map((toDo) => (<ToDo key={toDo.id} {...toDo} />))}
//atom.tsx
export const toDoSelector = selector({
key: "toDoSelector",
get: ({ get }) => {
const toDos = get(toDoState);
const category = get(categoryState);
return toDos.filter((toDo) => toDo.category === category);
},
});
selector가 toDos와 category를 받아서 category에 따라 toDo를 분류해준다.
현재는 Done 카테고리에 있어도 toDo를 추가하면 바로 나타나지 않는다. 자동으로 "TODO"리스트에 들어가는데, 다음 챕터에서는 toDo를 추가할 때 지금 category에 바로 넣을 수 있도록 해보자.
지금은 새 toDo를 추가할 때, 매번 "TODO" 카테고리로 들어가게 된다. 이번 챕터에서는 toDo의 카테고리가 categoryState에 따라서 추가되게 해보자!
//CreateToDo.tsx
const category = useRecoilValue(categoryState);
const onSubmit = ({ toDo }: IForm) => {
setToDos((oldToDos) => [
{ text: toDo, id: Date.now(), category: category },
...oldToDos,
]);
setValue("toDo", "");
};
//atom.tsx
type categories = "TODO" | "DOING" | "DONE";
export const categoryState = atom<categories>({
key: "category",
default: "TODO",
});
//ToDoList.tsx
const onInput = (event: React.FormEvent<HTMLSelectElement>) => {
setCategory(event.currentTarget.value as any);
};
CreateToDo.tsx에서의 category:category
는 category
로 써도 된다.
이렇게 쓰면 에러가 나는데, atom.tsx에서 categoryState 타입을 써주면 에러가 해결된다.
또한 ToDoList.tsx에서 setCategory에서도 오류가 나는 것을 볼 수 있는데 이유는 setCategory 함수를 호출 할때 인자로 타입이 string인 값을 넘기고 있기 때문이다.
이 문제를 해결하려면 as any
를 적어주면 된다. 타입스크립트가 보기에 option의 value는 그냥 string 이기 때문!
export enum Categories {
"TODO",
"DOING",
"DONE",
}
export const categoryState = atom<Categories>({
key: "category",
default: Categories.TODO,
});
ToDo.tsx의 name에서 오류가 나는데, 그 전에 확인할 내용이 있다.
이렇게 enum은 개발자의 코딩을 쉽게 해주는 도구로써 숫자임을 알 수 있다.
export const categoryState = atom<Categories>({
key: "category",
default: Categories.TODO , // default:0
});
즉, Categories.TODO
는 0
과 같다.
원한다면 enum에서의 타입을 바꿀 수 있다.
export enum Categories {
"TODO"="TODO",
"DOING"="DOING",
"DONE"="DONE",
}
위처럼 작성하면 TODO는 값은 실제로 숫자가 아니며 "TODO"가 될 것이다.