使用 TypeScript 和 React Query 构建待办事项列表:一份全面的指南
介绍:
在 Web 开发领域,构建待办事项应用是必经之路。本教程将更进一步,集成 TypeScript 以确保类型安全,并利用 React Query 实现高效的数据管理。最终,您将拥有一个功能齐全的待办事项应用,它具备强大的错误处理机制、实时更新功能以及类型安全的代码。
步骤 1:设置您的项目
首先,让我们创建一个新的 React 项目,其中包含 TypeScript 和 React Query。我们将使用 Vite 来进行快速开发并搭建现代化的构建环境。
npm init vite@latest my-todo-list --template react-ts
之后,您需要选择 React 作为我们的选项,然后选择 TypeScript + SWC(Speedy Web Compiler)。您可以通过此链接了解更多详情:https://www.dhiwise.com/post/maximize-performance-how-swc-enhances-vite-and-react
。完成这些步骤后,您需要切换到创建的项目目录并安装依赖项。
# Change directory
cd my-todo-list
# install dependencies
npm install
# Install React Query
npm install react-query@latest
步骤 2:在项目中配置 ReactQuery
为了使 React 查询正常工作,请确保已使用 QueryClientProvider 包装您的应用程序并提供了一个 QueryClient 实例。因此,您的 main.tsx 文件将如下所示:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import { QueryClient, QueryClientProvider } from "react-query";
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>
);
步骤 3:创建待办事项组件
我们的首要任务是创建一个待办事项组件,用于展示各个待办事项。每个待办事项都将包含一个用于跟踪完成情况的复选框和一个用于删除的按钮。
但在此之前,我们需要在 src 目录下创建一个名为 components 的新文件夹,我们将把本教程中要用到的所有组件都添加到这个文件夹中。
interface TodoProps {
id: number;
text: string;
completed: boolean;
onDelete: (id: number) => void;
onCompleteToggle: (id: number) => void;
}
const Todo = ({ id, text, completed, onDelete, onCompleteToggle }: TodoProps) => {
return (
<div className={`todo ${completed ? "done" : "in-progress"}`}>
<div className="todo-actions">
<input type="checkbox" checked={completed} onChange={() => onCompleteToggle(id)} />
<button onClick={() => onDelete(id)}>Delete</button>
</div>
<div>{text}</div>
</div>
);
};
export default Todo;
步骤 4:创建服务文件
为了从外部 API 获取待办事项,我们将利用 React Query 的 useQuery hook。这将使我们能够高效地管理数据获取和缓存。为此,我们将创建一个名为 Services 的文件夹,并在其中添加一个名为 api.ts 的文件,该文件将包含我们所有的 API 请求函数。
// services/todoAPI.ts
const API_URL = "https://dummyjson.com/todos";
export const fetchTodos = async () => {
const response = await fetch(API_URL);
return response.json();
};
export const toggleTodoCompletion = async (id: number) => {
try {
const response = await fetch(`${API_URL}/${id}`, {
method: "PATCH",
headers: {
"Content-type": "application/json; charset=UTF-8",
},
body: JSON.stringify({ completed: true }),
});
// Check if the request was not successful
if (!response.ok) {
throw new Error(`Failed to toggle completion status. Status: ${response.status}`);
}
// Parse response data
const data = await response.json();
// Return status and data
return {
status: response.status,
data: data,
};
} catch (error) {
// Handle errors
console.error("Error toggling completion:", error);
throw error;
}
};
// services/todoAPI.ts
export const deleteTodo = async (id: number) => {
try {
const response = await fetch(`${API_URL}/${id}`, {
method: "DELETE",
});
// Check if the request was successful
if (!response.ok) {
throw new Error(`Failed to delete todo. Status: ${response.status}`);
}
// Return status and data
return {
status: response.status,
data: await response.json(),
};
} catch (error) {
// Handle errors
console.error("Error deleting todo:", error);
throw error;
}
};
步骤 5:创建待办事项列表组件
我们将在该组件中实现获取、更新和删除数据所需的功能。
我们将使用此 React 查询提供的两个钩子。
-
useQuery:查询可以与任何基于 Promise 的方法(包括 GET 和 POST 方法)一起使用,以从服务器获取数据。
-
useMutation:如果您的方法修改服务器上的数据,我们建议改用 Mutation。
我们将首先从服务器获取数据。
const { data, isLoading, isError } = useQuery("todos", fetchTodos, { staleTime: 60000 });
让我们尝试解耦这行代码。
1.“todos”:是查询的唯一标识符,每个查询都应该有一个唯一的标识符。
2. fetchTodos:是我们定义在 api.ts 文件中的函数
// services/api.ts
const API_URL = "https://dummyjson.com/todos";
export const fetchTodos = async () => {
const response = await fetch(API_URL);
return response.json();
};
3. staleTime:如果您有一个数据列表,且该列表不经常更改,您可以指定一个过期时间(x 秒)。这意味着 React Query 只有在距离上次获取数据超过 x 秒后才会从服务器获取数据。
所以从服务器获取数据后,我们只需要显示待办事项列表即可。
const { data, isLoading, isError } = useQuery("todos", fetchTodos, { staleTime: 60000 });
if (isLoading) return <div>Loading...</div>;
if (isError) return <div>Error fetching todos</div>;
return (
<div className="todo-list">
{data?.todos.map((obj: TodoType) => (
<Todo
key={obj.id}
id={obj.id}
completed={obj.completed}
text={obj.todo}
/>
))}
</div>
);
};
从服务器获取数据后,我们将实现删除和更新功能。
在这个函数中,我们将使用Mutation钩子。
const UpdateTodoStatus = useMutation({
mutationFn: toggleTodoCompletion,
onSuccess: (res) => {
// Invalidate and refetch
if (res.status === 200) {
queryClient.invalidateQueries("todos");
}
},
});
UpdateTodoStatus mutation 函数是使用 React Query 的 useMutation hook 创建的。此函数负责切换待办事项的完成状态。它接受一个包含两个属性的对象作为参数:
1. mutationFn:此属性指定负责执行变更的函数,在本例中为 toggleTodoCompletion 函数。toggleTodoCompletion 函数向服务器发送 PATCH 请求以更新待办事项的完成状态。
export const toggleTodoCompletion = async (id: number) => {
try {
const response = await fetch(`${API_URL}/${id}`, {
method: "PATCH",
headers: {
"Content-type": "application/json; charset=UTF-8",
},
body: JSON.stringify({ completed: true }),
});
// Check if the request was not successful
if (!response.ok) {
throw new Error(`Failed to toggle completion status. Status: ${response.status}`);
}
// Parse response data
const data = await response.json();
// Return status and data
return {
status: response.status,
data: data,
};
} catch (error) {
// Handle errors
console.error("Error toggling completion:", error);
throw error;
}
};
2. onSuccess:此属性定义了一个回调函数,该函数在 mutation 成功时执行。在此回调函数中,我们会检查响应状态是否为 200,以表明 mutation 已成功。如果状态为 200,我们会使用 queryClient.invalidateQueries("todos") 使 React Query 缓存中的 "todos" 查询失效。这将触发 todos 数据的重新获取,确保在切换待办事项的完成状态后,UI 会更新为最新的更改。
删除操作与更新操作类似。
const DeleteTodo = useMutation({
mutationFn: deleteTodo,
onSuccess: (res) => {
// Invalidate and refetch
if (res.status === 200) {
queryClient.invalidateQueries("todos");
}
},
});
// services/api.ts
export const deleteTodo = async (id: number) => {
try {
const response = await fetch(`${API_URL}/${id}`, {
method: "DELETE",
});
// Check if the request was successful
if (!response.ok) {
throw new Error(`Failed to delete todo. Status: ${response.status}`);
}
// Return status and data
return {
status: response.status,
data: await response.json(),
};
} catch (error) {
// Handle errors
console.error("Error deleting todo:", error);
throw error;
}
};
注意:我们使用 dummyjson API 获取数据,因此对于删除和更新操作,您不会在服务器端看到任何变化,这只是一个模拟过程
。dummyjson.com
以下是待办事项列表组件的完整代码
import { QueryClient, useMutation, useQuery } from "react-query";
import Todo from "./Todo";
import { deleteTodo, fetchTodos, toggleTodoCompletion } from "../services/api";
const queryClient = new QueryClient();
interface TodoType {
id: number;
todo: string;
completed: boolean;
}
const TodoList = () => {
const { data, isLoading, isError } = useQuery("todos", fetchTodos, { staleTime: 60000 });
// Mutations
const UpdateTodoStatus = useMutation({
mutationFn: toggleTodoCompletion,
onSuccess: (res) => {
// Invalidate and refetch
if (res.status === 200) {
queryClient.invalidateQueries("todos");
}
},
});
const DeleteTodo = useMutation({
mutationFn: deleteTodo,
onSuccess: (res) => {
// Invalidate and refetch
if (res.status === 200) {
queryClient.invalidateQueries("todos");
}
},
});
if (isLoading) return <div>Loading...</div>;
if (isError) return <div>Error fetching todos</div>;
return (
<div className="todo-list">
{data?.todos.map((obj: TodoType) => (
<Todo
key={obj.id}
id={obj.id}
completed={obj.completed}
text={obj.todo}
onDelete={(id: number) => DeleteTodo.mutate(id)} // Call handleDeleteTodo
onCompleteToggle={(id: number) => UpdateTodoStatus.mutate(id)}
/>
))}
</div>
);
};
TodoList.propTypes = {};
export default TodoList;
结论:
通过本教程,我们利用 React Query 处理数据获取、变更和缓存,从而为待办事项管理提供了流畅的体验。凭借其声明式 API 和强大的缓存功能,React Query 简化了状态管理和数据获取,使您能够专注于构建卓越的用户体验。
要访问此项目的完整代码并进行更深入的探索,您可以在我的GitHub仓库中找到它。
文章来源:https://dev.to/wassim93/building-a-todo-list-with-typescript-and-react-query-a-compressive-guide-acj