构建乐观更新的数据表
介绍
今天,我将分享如何使用现代 react 模式构建一个精美的食品数据库管理系统。我们将专注于创建一个具有无缝乐观更新的响应式数据表,将 tanstack query(以前称为 react query)的强大功能与 mantine 的组件库相结合。
项目概况
要求
- 在数据表中显示食品
- 添加新项目并立即反馈
- 优雅地处理加载和错误状态
- 提供流畅的乐观更新
技术堆栈
- tanstack 查询:服务器状态管理
- mantine ui:组件库和表单管理
- mantine react table:高级表功能
- wretch:干净的 api 调用
- typescript:类型安全
实施指南
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 应用程序中实施乐观更新时面临哪些挑战?在下面的评论中分享您的经验。
以上就是构建乐观更新的数据表的详细内容,更多请关注其它相关文章!