介绍
今天,我将分享如何使用现代 react 模式构建一个精美的食品数据库管理系统。我们将专注于创建一个具有无缝乐观更新的响应式数据表,将 tanstack query(以前称为 react query)的强大功能与 mantine 的组件库相结合。
项目概况
要求
- 在数据表中显示食品
- 添加新项目并立即反馈
- 优雅地处理加载和错误状态
- 提供流畅的乐观更新
技术堆栈
- tanstack 查询:服务器状态管理
- mantine ui:组件库和表单管理
- mantine react table:高级表功能
- wretch:干净的 api 调用
- :类型安全
实施指南
1. 设立基金会
首先,让我们定义我们的类型和 api 配置:
// types
export type getallfoods = {
id: number;
name: string;
category: string;
};
export type createnewfoodtype = pick<
getallfoods,
| 'name'
| 'category'
>;
// api configuration
export const api = wretch('<http://localhost:9999>').options({
credentials: 'include',
mode: 'cors',
headers: {
'content-type': 'application/json',
accept: 'application/json',
},
});
// tanstack query
export const getfoodoptions = () => {
return queryoptions({
querykey: ['all-foods'],
queryfn: async () => {
try {
return await api.get('/foods')
.unauthorized(() => {
console.log('unauthorized');
})
.json<array<getallfoods>>();
} catch (e) {
console.log({ e });
throw e;
}
},
});
};
export const usegetallfoods = () => {
return usequery({
...getfoodoptions(),
});
};
登录后复制
2. 构建数据表
使用 mantine react table 的表格组件:
const foodsview = () => {
const { data } = usegetallfoods();
const columns = usememo<mrt_columndef<getallfoods>[]>(
() => [
{
accessorkey: 'id',
header: 'id',
},
{
accessorkey: 'name',
header: 'name',
},
{
accessorkey: 'category',
header: 'category',
},
// ... other columns
],
[]
);
const table = usemantinereacttable({
columns,
data: data ?? [],
// optimistic update animation
mantinetablebodycellprops: ({ row }) => ({
style: row.original.id < 0 ? {
animation: 'shimmer-and-pulse 2s infinite',
background: `linear-gradient(
110deg,
transparent 33%,
rgba(83, 109, 254, 0.2) 50%,
transparent 67%
)`,
backgroundsize: '200% 100%',
position: 'relative',
} : undefined,
}),
});
return <mantinereacttable table={table} />;
};
登录后复制
3. 创建表单
用于添加新食物的表单组件:
const createnewfood = () => {
const { mutate } = usecreatenewfood();
const forminputs = [
{ name: 'name', type: 'text' },
{ name: 'category', type: 'text' },
];
const form = useform<createnewfoodtype>({
initialvalues: {
name: '',
category: '',
// ... other fields
},
});
return (
<box mt="md">
<form onsubmit={form.onsubmit((data) => mutate(data))}>
<flex direction="column" gap="xs">
{forminputs.map((input) => (
<textinput
key={input.name}
{...form.getinputprops(input.name)}
label={input.name}
tt="uppercase"
type={input.type}
/>
))}
<button type="submit" mt="md">
create new
</button>
</flex>
</form>
</box>
);
};
登录后复制
4. 实施乐观更新
我们实现的核心 – tanstack 查询突变与乐观更新:
export const usecreatenewfood = () => {
const queryclient = usequeryclient();
return usemutation({
mutationkey: ['create-new-food'],
mutationfn: async (data: createnewfoodtype) => {
await new promise(resolve => settimeout(resolve, 3000)); // demo delay
return api.url('/foods').post(data).json<getallfoods>();
},
onmutate: async (newfood) => {
// cancel in-flight queries
await queryclient.cancelqueries({ querykey: ['all-foods'] });
// snapshot current state
const previousfoods = queryclient.getquerydata<getallfoods[]>(['all-foods']);
// create optimistic entry
const optimisticfood: getallfoods = {
id: -math.random(),
...newfood,
verified: false,
createdby: 0,
createdat: new date().toisostring(),
updatedat: new date().toisostring(),
};
// update cache optimistically
queryclient.setquerydata(['all-foods'], (old) =>
old ? [...old, optimisticfood] : [optimisticfood]
);
return { previousfoods };
},
onerror: (err, _, context) => {
// rollback on error
if (context?.previousfoods) {
queryclient.setquerydata(['all-foods'], context.previousfoods);
}
},
onsettled: () => {
// refetch to ensure consistency
queryclient.invalidatequeries({ querykey: ['all-foods'] });
},
});
};
登录后复制
5. 动画风格
动画将我们乐观的更新带入生活:
@keyframes shimmer-and-pulse {
0% {
background-position: 200% 0;
transform: scale(1);
box-shadow: 0 0 0 0 rgba(83, 109, 254, 0.2);
}
50% {
background-position: -200% 0;
transform: scale(1.02);
box-shadow: 0 0 0 10px rgba(83, 109, 254, 0);
}
100% {
background-position: 200% 0;
transform: scale(1);
box-shadow: 0 0 0 0 rgba(83, 109, 254, 0);
}
}
登录后复制
最佳实践
- 乐观更新
- 立即更新 ui,以获得更好的用户体验
- 通过回滚处理错误情况
- 通过适当的失效保持数据一致性
- 类型安全
- 使用 typescript 以获得更好的可维护性
- 为数据结构定义清晰的接口
- 尽可能利用类型推断
- 性能
- 更新期间取消正在进行的查询
- 使用正确的查询失效
- 实施高效的表单状态管理
- 用户体验
- 提供即时反馈
- 显示加载状态
- 优雅地处理错误
未来的增强功能
在您的实施中考虑这些改进:
- 撤消/重做功能
- 表单验证规则
- 错误边界实现
结果

完成请求后

结论
此实现演示了如何使用现代 react 模式创建强大的数据管理系统。 tanstack query、mantine ui 和深思熟虑的乐观更新的结合创造了流畅和专业的用户体验。
记住:
- 让你的组件保持专注且可维护
- 处理所有可能的状态(加载、错误、成功)
- 使用 typescript 提高代码质量
- 在实施中考虑用户体验
您在 react 应用程序中实施乐观更新时面临哪些挑战?在下面的评论中分享您的经验。
以上就是构建乐观更新的数据表的详细内容,更多请关注米云其它相关文章!
