23/12/27 typescript ,react-query 복습

2023. 12. 28. 08:36카테고리 없음

타입스크립트로 한 투두리스트 리액트쿼리를 곁들인 

 

<Nav.tsx> 네브를 따로 컴포넌로 만들었다.

import React, { useState } from "react";
import styled from "styled-components";
// import { __addTodo } from "../redux/mo/modules/todoSlice";
import { nanoid } from "nanoid";
import { useMutation, useQueryClient } from "@tanstack/react-query";   //유즈 뮤테이션과 유즈 쿼리 클라이언트 적용
import { addTodos } from "../redux/mo/modules/queryFns";   //쿼리펑션폴더에 넣어놓은 쿼리펑션

export default function Nav() {
  type T = { id: string; title: string; content: string; isDone: boolean };    //객체형인 투두하나의 타입지정
  const JSON_SERVER_BASE_URL = "http://localhost:4000/todos";   //인터셉터 굳이 안만들고 이렇게 놔둠 ㅋ
  const initialForm = {
    id: "",
    title: "",
    content: "",
    isDone: false,
  };
  const [formState, setFormState] = useState<T>(initialForm); //제네릭안에 만들어놓은 타입T 

  const queryClient = useQueryClient();
  const { mutate: mutateToAdd } = useMutation({      //CRUD중 CUD를 하기위한 훅
    mutationFn: addTodos,
    onSuccess: async () => {
      await queryClient.invalidateQueries({ queryKey: ["todos"] }); //얘가 캐쉬를 다시 가져옴
    },
  });

  const OnchangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {   //요 타입지정 어려움 ㅜ
    const { name, value } = e.target;         //캬 내가 했지만 요 코드 이쁘네
    setFormState((prev: T) => ({ ...prev, [name]: value })); 
  };
  const OnSubmitHandler = async (e: React.FormEvent<HTMLFormElement>) => {  //파라미터 옆에 : 하고 타입인데
    const newTodo = {                                              // 익숙치 않네 
      id: nanoid(),
      title: formState.title,
      content: formState.content,     //유즈스테이트에서 받아온값 사용
      isDone: false,
    };
    e.preventDefault();

    mutateToAdd(newTodo);       //여기서 유즈뮤테이트 사용
    setFormState(initialForm);     //이니셜스테이트 초기화
  };

  return (
    <Header onSubmit={OnSubmitHandler}> //헤더를 폼태그로 만들었었네 ㅋㅋㅋ
      <span>제목</span>{" "}
      <input
        name="title"
        value={formState.title}
        onChange={OnchangeHandler}
      ></input>
      <span>내용</span>{" "}
      <input
        name="content"
        value={formState.content}
        onChange={OnchangeHandler}
      ></input>
      <br />
      <button disabled={!formState.title || !formState.content}> //해당 인풋에 요값 안넣으면 불능만듦
        추가하기
      </button>
    </Header>
  );
}
const Header = styled.form`
  background-color: lightcyan;
  height: 150px;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
`;

 

<TodosLIst.tsx> 메인페이지이면서 유일한 페이지 하하 

import React from "react";
import styled from "styled-components";
// import {
//   __deleteTodo,                          //요거는 thunk를 사용한 흔적  // 아니 thunk쓰다가 리액트쿼리쓰니까 
//   __editTodo,                             //훠월씬 쉬움
//   __getTodos,
// } from "../redux/mo/modules/todoSlice";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { deleteTodo, editTodo, getTodos } from "../redux/mo/modules/queryFns";

export default function TodosList({ listType }: { listType: boolean }) {
  type T = { id: string; title: string; content: string; isDone: boolean };  //네브때와 동일한 타입 굳이 파일분리 안함 ㅋ

  const JSON_SERVER_BASE_URL = "http://localhost:4000/todos";

  const { data: todos, isLoading } = useQuery({       //유즈쿼리훅 사용법 안에 는 isloading이라는 키가 있다.
    queryKey: ["todos"],                                            //isFetching도 있다던데?
    queryFn: getTodos,
  });
  const queryClient = useQueryClient();             //유즈뮤테이트때 쓰기위해 쿼리클라이언트 만들어야함
  const { mutate: mutateToDelete } = useMutation({
    mutationFn: deleteTodo,
    onSuccess: async () => {
      await queryClient.invalidateQueries({ queryKey: ["todos"] });
    },
  });
  const { mutate: mutateToEdit } = useMutation({
    mutationFn: editTodo,                                                //요 앞에 뮤테이션 함수는 파일분리 해놓음 이따 보여줌
    onSuccess: async () => {
      await queryClient.invalidateQueries({ queryKey: ["todos"] });
    },
  });

  const DeleteHandler = (id: string) => {
    const confirmedData = window.confirm("정말로 삭제할꺼에여?");
    if (confirmedData) {
      mutateToDelete(id);               //인자로 id가 필요해서 넘겨주고
    }
  };
  const UpdateHandler = (id: string, isDone: boolean) => {
    mutateToEdit({ id, isDone });                                  //여기는 객체형으로 id와 이즈돈 넘겨주고
  };

  const notYetTodos = todos?.filter((todo: T) => {
    return todo.isDone === false;                                             //요 두개의 필터는 투두스를 두개로 나눴다.
  });
  const completedTodos = todos?.filter((todo: T) => {
    return todo.isDone === true;
  });

  const changedTodos = listType ? completedTodos : notYetTodos;

  if (isLoading) {
    return <>로딩중....</>;
  }

  return (
    <>
      {" "}
      <div style={{ backgroundColor: "black", color: "white" }}>
        {listType ? "완료된투두" : "해야할투두"}
      </div>
      <Section1>
        <>
          {changedTodos?.map((todo: T) => {
            return (
              <ListWrapper key={todo.id}>
                <div>아이디 : {todo.id}</div>
                <div>제목 : {todo.title}</div>
                <div>내용 : {todo.content}</div>
                <div>상태 : {todo.isDone ? "완료" : "미완료"}</div>
                <button onClick={() => UpdateHandler(todo.id, todo.isDone)}>    //온클릭주고 필요한 인자 넘겨주고
                  {listType ? "취소" : "완료"}
                </button>
                <button onClick={() => DeleteHandler(todo.id)}>삭제하기</button>
              </ListWrapper>
            );
          })}
        </>
      </Section1>
    </>
  );
}
const Section1 = styled.section`
  height: 200px;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  gap: 10px;
  background-color: lightblue;
`;
const ListWrapper = styled.div`
  height: 180px;
  width: 180px;
  background-color: lightgoldenrodyellow;
`;

 

<App.tsx> 이 파일을 먼저 보여줬어야 하나?

import React from "react";
import "./App.css";
import styled from "styled-components";
import Nav from "./pages/Nav";
import TodosList from "./pages/TodosList";

function App() {
  type T = { id: string; title: string; content: string; isDone: boolean };
  // const [todos, setTodos] = useState<T[]>([]);

  console.log();

  return (
    <Container className="App">
      {" "}
      <Content>
        <ZeMOK>투두리스트</ZeMOK>
        <Nav />                                                  //네브컴포넌트달고
        <TodosList listType={false} />             //여기에 투두리스트를 두번 달았다.
        <TodosList listType={true} />             //liistType에 불리언 값에 따라 랜더링하는 모양이 달라지게함
      </Content>                                     //위에 파일가보면 props 받은게 보일꺼임
    </Container>
  );
}

export default App;

const Container = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
`;

const Content = styled.div`               //씨에스에스는 평범한거 같음
  width: 100%;
  height: 100vh;
  max-width: 1200px;
  min-width: 800px;
`;  
const ZeMOK = styled.div`
  background-color: lightcoral;
  font-size: 50px;
  height: 100px;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
`;

// const Section2 = styled.section`
//   background-color: lightpink;
//   height: 200px;
//   display: flex;
//   justify-content: flex-start;
//   align-items: center;
//   gap: 10px;
// `;

// const ListWrapper = styled.div`
//   height: 180px;
//   width: 180px;
//   background-color: lightgoldenrodyellow;
// `;

 

 

<queryFn.ts> 쿼리펑션과 뮤테이트펑션을 한 파일에 담아둠 나눌 필요가 있나?

import axios from "axios";

type T = { id: string; title: string; content: string; isDone: boolean };

const JSON_SERVER_BASE_URL = "http://localhost:4000/todos";

export const getTodos = async () => {
  const { data } = await axios.get<T[]>(JSON_SERVER_BASE_URL);  //쿼리펑션 겟투두스
  return data;
};

export const addTodos = async (newTodo: T) => {                    //뮤테이트 펑션인 add투두
  await axios.post(JSON_SERVER_BASE_URL, newTodo);
};

export const deleteTodo = async (id: string) => {                     //뮤테이트 펑션인 delete투두
  await axios.delete(`${JSON_SERVER_BASE_URL}/${id}`);   //인자로 넘겨운 아이디 타입 지정해주고
};

export const editTodo = async ({                               //edit투두 //넘어온 객체 타입지정 해주고
  id,
  isDone,
}: {
  id: string;
  isDone: boolean;
}) => {
  await axios.patch(`${JSON_SERVER_BASE_URL}/${id}`, {        //아이디 일치하는에 isDone만 바꿔주고
    isDone: !isDone,
  });
};

//요번 과제할때 thunk나 query적용 때문에 빡치는게 아니라

//타입스크립트 때문에 뚜껑 여러번 열림 와 진짜 익숙해지면 오류해결엔 용이할텐데

//코드 자체를 짤때 타입스크립트 처음해보니까 자꾸 컴파일러가 뭐라해서 딥빡

//내일 올릴꺼 없으면 thunk 버전도 올리겠슴다. 하하