1.8 React masterClass (ToDoList2)

hun2__2·2022년 1월 8일
0

Have a fruitful vacation

목록 보기
12/24

먼저 어제 하다 만 ToDoList를 CreateToDo component와 toDo componenet로 나눠줄 것이다.

CreateToDo.tsx에서 input 값을 atom의 state에 넣어 줄 것이므로
useSetRecoilState(toDoState)를 사용해서 넣어주고
form에 해당하는 부분은 따로 빼와서 input값을 받게 해준다.

interface IForm {
  toDo: string;
}

function CreateToDo() {
  const {
    register,
    handleSubmit,
    setValue,
    formState: { errors },
  } = useForm<IForm>();

  const setToDos = useSetRecoilState(toDoState);
  const valid = ({ toDo }: IForm) => {
    console.log(toDo);
    setToDos((oldToDos) => [
      { text: toDo, id: Date.now(), category: "TO_DO" },
      ...oldToDos,
    ]);
    setValue("toDo", "");
  };
  return (
    <Form onSubmit={handleSubmit(valid)}>
      <input
        {...register("toDo", { required: "toDoList를작성해주세요" })}
        placeholder="ToDoList"
      />
      <span>{errors?.toDo?.message}</span>
      <button>add</button>
    </Form>
  );
}

그리고 List들을 보여주기 위해 toDos?.map을 사용해서 toDo component를 불러온다
이때 각 obj에 들어있는 값(text, id, category)를 모두 props로 넘겨줘야 한다
text= {todo.text} id = {todo.id} category={todo.category}
이렇게 작성 해도 되지만 ES6문법을 사용해서 {...todo}로 간결화 할 수 있다.

	<ul>
    	{toDos?.map((todo,idx) => <ToDo key={idx} {...todo}/ >)}
    </ul>

이제 ToDo.tsx를 꾸며야 한다.
이 안에 뭐가 들어가야 할까?
먼저 input으로 받은 text를 보여주고 category를 보여줘서 변경하는 기능이 필요하다.

    <Li>
      <span>{text} </span>
      {category !== "TO_DO" && (
        <button>To Do</button>
      )}
      {category !== "DOING" && (
        <button>Doing</button>
      )}
      {category !== "DONE" && (
        <button>Done</button>
      )}
    </Li>

이렇게 각각의 버튼을 text와 함께 만들어주고 현재 카테고리가 아닌 것들만 보이도록 해준다.

그리고 이제 해당 todo의 category를 변경할 수 있도록 해준다.

그러기 위해서는
1. 바꿀 todo값을 선택하고
2. 선택한 todo의 category값을 변경해준다.

1을 하기 위해서는 category를 바꾸려는 target의 id와 atom state에 저장된 id가 동일해야한다.
JS의 findIndex를 사용해서 toDo state 배열에 들어있는 obj들의 id 중 ToDo.tsx가 props로 넘겨 받은 id가 동일한 obj를 찾아준다.

const targetIndex = oldTodos.findIndex((todo) => todo.id === id);

2을 하기 위해서는 해당 target의 category를 바꾼 값으로 새로 만든 배열을 다시 atom에 저장해야 한다.
1에서 찾은 obj의 카테고리를 변경한 후 원래 배열의 해당obj index에 새로만든 obj로 변경해서 반환해준다.

이때 주의할 점은 state값은 직접 접근해서 변경할 수 없다. setState를 이용해서 새로운 값을 만들어서 state를 변경해주는 식으로 해야한다.
따라서 splice(targetIndex,1, newObj) 와같은 식으로 할 수 없다.

따라서

  const setToDos = useSetRecoilState(toDoState);
  
    setToDos((oldTodos) => {
      const targetIndex = oldTodos.findIndex((todo) => todo.id === id);
      const newTodo = { text, id, category: newCategory };
      return [
        ...oldTodos.slice(0, targetIndex),
        newTodo,
        ...oldTodos.slice(targetIndex + 1),
      ];
    });

와 같이 useSetRecoilState 를 이용해서 기존의 iedex만 새로운 todo로 바꾼 값으로 변경해주면 완성!


이렇게 컴포넌트를 나누고 각각의 버튼을 누를때 카테고리를 바꿀 수 있다.

이제 reocil의 selector을 사용해 볼 것이다.
공식 문서에서

selector는 derived state를 나타냅니다.
derived state는 어떤 방법으로든 주어진 상태를 수정하는 순수 함수에 전달된 상태의 결과물로 생각할 수 있다.

라고한다. 쉽게 말해 state를 내가 원하는 형태로 변형 시킬 수 있다. 즉 atom의 반환값을 내가 원하는 방식으로 바꿀 수 있다. 기존 atom의 state는 그대로 유지 된다!

이것을 이용해서 atom의 default에 들어가 있는 todoList들을 카테고리로 나눠줄 수 있다.

먼저 atom값을 받아와 카테고리별로 필터링해준 값을 반환하는 selector를 만들어야 한다.

selector에는
ReadOnlySelectorOptions타입과
ReadWriteSelectorOptions타입이 있다
먼저 only타입에는
key값과 get 함수를 포함한 obj를 인자로 받는다 타입은 아래와 같다

get함수에는 인자로 받을 수 있는 option이 get, getCallback 두가지가 있다.

write타입에는 only타입을 상속받고 set 함수를 추가로 사용한다.
이건 나중에 더 살펴보자

cf) selector 타입들

먼저 get함수를 사용해서 atom으로부터 state를 넘겨받고 그 값을 내가 원하는 형태로 가공해준다.

export const toDoSelecor = selector({
  key: "toDoSelecor",
  get: ({ get }) => {
    const toDos = get(toDoState);
    return [
      toDos.filter((todo) => todo.category === "TO_DO"),
      toDos.filter((todo) => todo.category === "DOING"),
      toDos.filter((todo) => todo.category === "DONE"),
    ];
  },
});

그리고 atom을 사용한것과 마찬가지로 useRecoilValue, useSetRecoilState, useRecoilState 등을 사용해서 state의 값을 get, set 해 줄 수 있다.

그러면 atom의 default에 있는 data는 그대로 있고
그 data를 selector에서 내가 원하는 형태로 가공해서 반환해준다.
todoList에서 연결되있는 atom값을 selector로 반환한 값으로 변경시켜 준다.

// const toDos = useRecoilValue(toDoState);
const [toDo, doing, done] = useRecoilValue(toDoSelecor);

그러면 각각의 category에 맞게 들어가있는 배열을 반환받는다.
새로반환받은 값을 각각의 ul태그로 묶어 ToDo component로 data를 보내준다면

      <ul>
        {toDo?.map((toDo, idx) => (
          <ToDo key={idx} {...toDo} />
        ))}
      </ul>
      <hr />

      <h2>Doing</h2>
      <ul>
        {doing?.map((toDo, idx) => (
          <ToDo key={idx} {...toDo} />
        ))}
      </ul>
      <hr />

      <h2>done</h2>
      <ul>
        {done?.map((toDo, idx) => (
          <ToDo key={idx} {...toDo} />
        ))}
      </ul>

아래와 같이 나눠진 값과 버튼을 눌러 catagory를변경되면 data가 변경되어 리렌더링이 일어나면서 위치가 바뀐다.

이제 여기서 내가 원하는 카테고리만 보이도록 바꿔주고 싶으면

category를 저장하는 state를 atom으로 하나 만들어주고

export const categoryState = atom({
  key: "categoryState",
  default: "TO_DO",
});

카테고리를 선택하면 선택된 카테고리만 보이도록 하드코딩할 수 있다.

  const [category, setCategory] = useRecoilState(categoryState);
  const onInput = (event: React.FormEvent<HTMLSelectElement>) => {
    setCategory(event.currentTarget.value);
  };
  
  return{
  	...
      {category === "TO_DO" &&
        toDo.map((todo, idx) => <ToDo key={idx} {...todo} />)}
      {category === "DOING" &&
        doing.map((todo, idx) => <ToDo key={idx} {...todo} />)}
      {category === "DONE" &&
        done.map((todo, idx) => <ToDo key={idx} {...todo} />)}
	}

그러면 내가 선택한 카테고리만 볼 수있도록 ToDoList를 만들어 줄 수 있다.

하지만 selector를 사용한다면 좀 더 클린하게 코드를 작성할 수 있다.

그건 내일 알아보자!


<추가>
1.9
위의 코드를 selecter를 이용해서 좀더 클린하게 짤 수 있다.

먼저 selector에서 category를 받아와서 toDo의 category와 동일한 것만 필터링한 값을 반환해주면

export const toDoSelecor = selector({
  key: "toDoSelecor",
  get: ({ get }) => {
    const toDos = get(toDoState);
    const category = get(categoryState);
    if (category === "TO_DO")
      return toDos.filter((toDo) => toDo.category === "TO_DO");
    if (category === "DOING")
      return toDos.filter((toDo) => toDo.category === "DOING");
    if (category === "DONE")
      return toDos.filter((toDo) => toDo.category === "DONE");
  },
});

모든 카테고리 별로 나눠진 값을 다 받아오지않고 선택한 값만 리턴받도록 하셔 간결하게 바꿔줄 수 있다.

const toDos = useRecoilValue(toDoSelecor);
      {toDos?.map((toDo, idx) => (
        <ToDo key={idx} {...toDo} />
      ))}

이제 여기서 한 단계 더 나아가면 category에 if문을 사용한 것을 보면 결국 categoryState안에있는 cateogry와 동일한 cateogry를 가진 toDo만 반환하는 것이므로

export const toDoSelecor = selector({
  key: "toDoSelecor",
  get: ({ get }) => {
    const toDos = get(toDoState);
    const category = get(categoryState);
    return toDos.filter((toDo) => toDo.category === category);
  },
});

이렇게 더 간결하게 바꿔줄 수 있다!

이렇게 하면 ToDoList에서 렌더링해할때 고려할 요소가 3개의 배열에서 1개의 배열로 줄었으므로 성능이 개선된다!

ps.
생각보다 진도가 늦어졌다... 이번주 까지 JS공부를 끝내고 다음주에 파이썬 챌린지를 준비하며 같이 하려고 했는데 TS와 다른 새로운 것들을 많이 배우며 진도가 늦어졌다... 그냥 내가 게을렀던것 같다,,ㅎ

어쩔 수 없이! 다음주에 계절학기도 끝나겠다 파이썬과 reactJS를 같이 공부하자!!!
홧팅홧팅

profile
과정을 적는 곳

0개의 댓글