ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Refine v5 数据获取完全指南:Data Provider、数据 Hooks 与关系管理实战

Refine v5 数据获取完全指南:Data Provider、数据 Hooks 与关系管理实战 Refine v5 数据获取完全指南Data Provider、数据 Hooks 与关系管理实战【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine数据获取是所有内部工具与 Admin Panel 应用的核心环节。本文以 Refine v5 官方指南《Data Fetching Guide》为骨架完整讲解 Refine 的DataProvider接口、useOne/useUpdate/useList等数据 Hooks、meta属性、多 Data Provider、错误处理与数据关系管理的全部实战细节并辅以仓库源码佐证。读完本文你将能够在 Refine 项目中自如地对接 REST、GraphQL 等任意 API并掌握过滤、排序、分页、乐观更新与认证请求的标准写法。Data Provider数据获取的基石任何 UI 应用本质上都是用户与底层数据源之间的桥梁。在 Refine 中管理数据依赖一个名为Data Provider的对象——它是一个实现了DataProvider接口的函数职责是与你的 API 通信并把数据提供给 Refine 应用使用。从源码结构看DataProvider是一个包含一组方法签名的 TypeScript 类型见 packages/core/src/contexts/data/types.ts其核心方法包括方法职责getList获取资源列表必选getOne获取单条记录必选getMany按 ID 批量获取多条记录可选create新建记录必选createMany批量新建可选update更新记录必选updateMany批量更新可选deleteOne删除单条记录必选deleteMany批量删除可选getApiUrl返回 API 地址必选custom发送自定义 API 请求可选Refine 会自动把resource资源名、记录的id等参数传递给 Data Provider使其能够向正确的端点发起请求。你可以直接使用官方内置的 Data Provider也可以针对自己的 API 编写一个全新的 Data Provider。一旦把 Data Provider 交给 Refine就可以通过useOne、useList、useUpdate等数据 Hooks 轻松管理来自各种数据源REST、GraphQL、RPC、SOAP的数据。官方示例还展示了如何将products端点交给RESTProvider、将users查询交给GraphQLProvider实现同应用内的混合数据源。使用 useOne 获取单条记录假设要从products端点获取 ID 为123的记录使用useOneHook 即可。它底层调用 Data Provider 的getOne方法。以下是官方指南中的完整示例源码见 documentation/docs/guides-concepts/data-fetching/use-one.tsx。App.tsx把 Data Provider 挂载到Refine /组件import React from react; import { Refine } from refinedev/core; import { Product } from ./product.tsx; import { dataProvider } from ./data-provider.ts; export default function App() { return ( Refine dataProvider{dataProvider(https://api.fake-rest.refine.dev)} Product / /Refine ); }data-provider.ts实现 Data Provider 的getOne方法import { DataProvider } from refinedev/core; export const dataProvider (url: string): DataProvider ({ getOne: async ({ id, resource }) { const response await fetch(${url}/${resource}/${id}); const data await response.json(); return { data, }; }, create: async () { throw new Error(Not implemented); }, update: async () { throw new Error(Not implemented); }, deleteOne: async () { throw new Error(Not implemented); }, getList: async () { throw new Error(Not implemented); }, getApiUrl: () url, });product.tsx在组件中使用useOne读取数据并通过query中的isLoading、isError、error处理加载与错误状态import React from react; import { useOne, BaseKey } from refinedev/core; export const Product: React.FC () { const { result: product, query: { error, isError, isLoading } } useOneIProduct({ resource: products, id: 123, }); if (isError) div{error?.message}/div; if (isLoading) divLoading.../div; return ( div h4{product?.name}/h4 pMaterial: {product?.material}/p pPrice {product?.price}/p /div ); }; interface IProduct { id: BaseKey; name: string; material: string; price: string; }注意从 v5 开始useOne的返回值中查询结果通过result暴露即product本身而data、isLoading、isError、error、isFetching等查询状态统一放在query对象中。使用 useUpdate 更新数据现在更新products端点中 ID 为124的记录。使用useUpdateHook它底层调用dataProvider.update方法。官方示例中更新的是产品的价格字段随机值完整代码见 documentation/docs/guides-concepts/data-fetching/use-update.tsx。data-provider.ts中实现update方法发送PATCH请求update: async ({ resource, id, variables }) { const response await fetch(${url}/${resource}/${id}, { method: PATCH, body: JSON.stringify(variables), headers: { Content-Type: application/json, }, }); const data await response.json(); return { data, }; },product.tsx中使用useUpdate触发变更import { useOne, BaseKey, useUpdate } from refinedev/core; const { result: product, query: { error, isError, isLoading, isFetching } } useOneIProduct({ resource: products, id: 124, }); const { mutate, mutation: { isPending: isUpdating } } useUpdate(); const incrementPrice async () { await mutate({ resource: products, id: 124, values: { price: Math.random() * 100, }, }); }; // 按钮上利用 isUpdating / isFetching 禁用避免重复提交 button onClick{incrementPrice} disabled{isUpdating || isFetching}Update Price/buttonuseUpdate的mutate接收resource、id、values三个关键参数values即要提交的字段对象会原样传给 Data Provider 的update方法作为variables参数。数据 Hooks 一览Refine 为 CRUD 操作提供了完整的数据 Hooks 集合下表整理自官方文档的>import { DataProvider, useOne } from refinedev/core; useOne({ resource: products, id: 1, meta: { foo: bar, }, }); export const dataProvider (apiUrl: string): DataProvider ({ getOne: async ({ resource, id, meta }) { const response await fetch(${apiUrl}/${resource}/${id}, { headers: { x-foo: meta.foo, }, }); const data await response.json(); return { data, }; }, ... });在>import gql from graphql-tag; import { useOne, useUpdate } from refinedev/core; const GET_PRODUCT_QUERY gql query GetProduct($id: ID!) { product(id: $id) { id title category { title } } } ; useOne({ resource: products, id: 1, meta: { gqlQuery: GET_PRODUCT_QUERY, }, }); const UPDATE_PRODUCT_MUTATION gql mutation UpdateOneProduct($id: ID!, $input: UpdateOneProductInput!) { updateOneProduct(id: $id, input: $input) { id title category { title } } } ; const { mutate } useUpdate(); mutate({ resource: products, id: 1, values: { title: New Title, }, meta: { gqlMutation: UPDATE_PRODUCT_MUTATION, }, });官方指南特别说明Nest.js QueryData Provider 对gqlQuery与gqlMutation字段提供了完整支持该 Provider 的源码位于 packages/nestjs-query其他 GraphQL 系 Data Provider如 packages/graphql、packages/hasura、packages/strapi-graphql也可以作为对接 GraphQL API 的起点。多 Data Provider同一应用对接多个 API在实际业务中一个应用往往需要对接多个 API。Refine 允许定义多个 Data Provider每个 Provider 可以有自己的配置从而在单个应用中管理复杂的数据场景。官方示例要同时获取products来自https://api.finefoods.refine.devuser来自https://api.fake-rest.refine.devApp.tsx中以对象形式传入多个 Providerdefault键是必选的默认 ProviderRefine dataProvider{{ default: dataProvider(API_URL), fineFoods: dataProvider(FINE_FOODS_API_URL), }} HomePage / /Refinehome-page.tsx中通过dataProviderName指定具体使用哪个 Providerconst { result: product, query: { isLoading: isLoadingProduct } } useOneIProduct({ resource: products, id: 123, dataProviderName: default, }); const { result: user, query: { isLoading: isLoadingUser } } useOneIUser({ resource: users, id: 123, dataProviderName: fineFoods, });完整示例代码见 documentation/docs/guides-concepts/data-fetching/multiple-data-provider.tsx。错误处理统一的 HttpError 契约Refine 期望 Data Provider 抛出的错误都继承自HttpError。统一错误接口让来自 API 的错误更易于处理。正确实现后Refine 会在错误处理上带来多个好处通知Notification如果配置了notificationProvider出错时 Refine 会自动弹出通知服务端校验Server-Side Validation把 API 返回的错误显示在对应表单字段上乐观更新Optimistic Updates发起变更时立即更新 UI若变更出错则自动回滚。官方错误处理示例见 documentation/docs/guides-concepts/data-fetching/error-handling.tsx展示了如何在getOne中构造并拒绝一个HttpErrorgetOne: async ({ id, resource }) { const response await fetch(${url}/${resource}/${id}); const data await response.json(); if (!response.ok || !data) { const error: HttpError { message: Something went wrong while fetching data, statusCode: 404, }; return Promise.reject(error); } return { data }; },组件侧则通过query.error与query.isError展示错误信息示例中还用queryOptions: { retry: 0 }关闭了 TanStack Query 的自动重试以便错误立即呈现。而在>getList: async ({ resource }) { const response await fetch(${url}/${resource}); const data await response.json(); return { data, total: data.length, }; },组件中渲染列表const { result } useList({ resource: products, }); const products result?.data; // products?.map(...) 渲染商品名称、价格、材质Filters、Sorters 与 Pagination获取数据的子集现实场景中通常只需获取数据的子集。Refine 在数据 Hooks 中提供了统一的filters、sorters、pagination参数并透传给 Data Provider 方法如何把这些参数转成请求由 Data Provider 负责。官方示例要获取5 个商品pagination: { currentPage: 1, pageSize: 5 }material字段等于woodenfilters按ID字段降序排序sorters: [{ field: id, order: DESC }]const { result } useList({ resource: products, pagination: { currentPage: 1, pageSize: 5 }, sorters: [{ field: id, order: DESC }], filters: [{ field: material, operator: eq, value: Wooden }], });Data Provider 侧负责把参数拼装成查询串完整示例见 documentation/docs/guides-concepts/data-fetching/use-list-with-filters.tsxgetList: async ({ resource, filters, pagination, sorters }) { const filter filters?.[0]; const sorter sorters?.[0]; const params []; if (filter field in filter) { params.push(${filter.field}${filter.value}); } if (sorter field in sorter) { params.push(_sort${sorter.field}); params.push(_order${sorter.order}); } // pagination 是可选的未定义时给出默认值 const { currentPage 1, pageSize 10 } pagination ?? {}; params.push(_start${(currentPage - 1) * pageSize}); params.push(_end${currentPage * pageSize}); const query params.join(); const response await fetch(${url}/${resource}?${query}); const data await response.json(); return { data, total: data.length, }; },复杂查询组合逻辑运算符利用filters与sorters还可以构建更复杂的查询。例如获取满足以下任一条件的商品材质为wooden且属于分类 ID45或价格在 1000 到 2000 之间。import { useList } from refinedev/core; useList({ resource: products, pagination: { currentPage: 1, pageSize: 10, }, filters: [ { operator: and, value: [ { field: material, operator: eq, value: wooden }, { field: category.id, operator: eq, value: 45 }, ], }, { operator: or, value: [ { field: price, operator: gte, value: 1000 }, { field: price, operator: lte, value: 2000 }, ], }, ], });CrudFilter支持eq、ne、gte、lte、contains等操作符也支持and/or组合。从>┌──────────────┐ ┌────────────────┐ │ Products │ │ ProductDetail │ │--------------│ │----------------│ │ id │───────│ id │ │ name │ │ weight │ │ price │ │ dimensions │ │ description │ │ productId │ │ detail │ │ │ └──────────────┘ └────────────────┘用useOne先取商品再用queryOptions.enabled控制第二个useOne在商品数据就绪后再请求详情见 documentation/docs/guides-concepts/data-fetching/one-to-one.tsxconst { result: product, query: { isLoading: productLoading } } useOneIProduct({ resource: products, id: 123, }); const { result: productDetail, query: { isLoading: productDetailLoading } } useOneIProductDetail({ resource: product-detail, id: product?.id, queryOptions: { enabled: !!product, }, });One-to-Many一对多一对多关系中每条记录匹配多条记录如同“一个家长多个孩子”。例如一个商品可以有多条评价┌──────────────┐ ┌────────────────┐ │ Products │ │ Reviews │ │--------------│ │----------------│ │ id │───┐ │ id │ │ name │ │ │ rating │ │ price │ │ │ comment │ │ description │ │ │ user │ │ detail │ └───│ product │ └──────────────┘ └────────────────┘用useList配合按商品 ID 过滤来获取某商品的评价见 documentation/docs/guides-concepts/data-fetching/one-to-many.tsxconst { result: reviewResult, query: { isLoading: reviewsLoading } } useListIProductReview({ resource: product-reviews, filters: [{ field: product.id, operator: eq, value: product?.id }], queryOptions: { enabled: !!product, }, });Many-to-Many多对多多对多关系中每条记录匹配多条记录而这些记录又各自匹配多条记录。例如商品与分类是多对多关系需要通过中间表ProductCategories关联┌──────────────┐ ┌───────────────────┐ ┌──────────────┐ │ Products │ │ ProductCategories │ │ Categories │ │--------------│ │-------------------│ │--------------│ │ id │───┐ │ id │ ┌───│ id │ │ name │ └───│ productId │ │ │ name │ │ price │ │ categoryId │───┘ │ description │ │ description │ │ │ │ │ └──────────────┘ └───────────────────┘ └──────────────┘这种场景下先用useList取中间表记录再用两次useMany分别取商品与分类并通过enabled控制依赖关系import { useList, useMany } from refinedev/core; const { result: { data: productCategories }, } useList({ resource: productCategories, }); const { result: { data: products }, } useMany({ resource: products, ids: productCategories.map((productCategory) productCategory.productId), queryOptions: { enabled: productCategories.length 0, }, }); const { result: { data: categories }, } useMany({ resource: categories, ids: productCategories.map((productCategory) productCategory.categoryId), queryOptions: { enabled: productCategories.length 0, }, });认证保护的数据获取当 API 需要登录凭证时首先通过authProvider.login获取认证 token随后把 token 附加到每次请求的 header 中并使用Authenticated /组件按登录状态渲染合适的内容详见 documentation/docs/guides-concepts/data-fetching/authentication.tsx。auth-provider.tslogin把 token 写入localStoragecheck据此判断认证状态export const authProvider (url: string): AuthProvider ({ login: async ({ email, password }) { localStorage.setItem(token, JSON.stringify({ email, password })); return { success: true }; }, check: async () { const token localStorage.getItem(token); return { authenticated: !!token, error: new Error(Unauthorized), }; }, logout: async () { localStorage.removeItem(token); return { success: true }; }, onError: async () { throw new Error(Not implemented); }, });data-provider.ts用 Axios 实例 请求拦截器为每个请求自动附加Authorization: Bearer tokenheaderimport axios from axios; const axiosInstance axios.create(); axiosInstance.interceptors.request.use( async (config) { const token localStorage.getItem(token); if (token config?.headers) { config.headers.Authorization Bearer ${token}; } return config; }, (error) Promise.reject(error), );home-page.tsx用useLogin/useLogout配合Authenticated /实现条件渲染Authenticated loading{loading} fallback{ div h4You are not authenticated/h4 button disabled{isPendingLogin} onClick{() login({ email: refinedemo.com, password: refine })} Login /button /div } div button onClick{() logout()}Logout/button {/* 登录后渲染数据列表 */} /div /Authenticated自定义 TanStack Query 的 QueryClient如果需要对 TanStack Query 的QueryClient实例进行定制例如全局配置重试、缓存时间等可以通过Refine /组件的reactQuery属性传入。自建 Data Provider完整接口实现参考官方指南提供了一份面向 JSON Placeholder API 的完整 Data Provider 实现见 documentation/docs/guides-concepts/data-fetching/data-provider-interface.md覆盖了getOne、update、create、deleteOne、getList五个方法值得逐行研读其中有几个关键设计请求方法与 meta 解耦meta中的method可以覆盖默认方法getOne默认get、update默认patch、create默认post、deleteOne默认delete统一错误转换Axios 响应拦截器把error.response.data.message与error.response.status组装成HttpError后 reject操作符映射mapOperator把 Refine 的CrudOperatorsne、gte、lte、contains、eq映射为 API 认识的查询后缀_ne、_gte、_lte、_like、空串分页默认值pagination可选默认{ currentPage: 1, pageSize: 10, mode: server }在server模式下转换为_start/_end查询参数。该文件还提示更全面、更多样的实现可以参考官方各 Data Provider 包的源码例如 packages/simple-rest/src/provider.ts。官方支持的 Data Provider 生态官方指南列出了 Refine 内置支持的 Data Provider见 supported-data-providers.md对应仓库中均有独立包Simple RESTpackages/simple-rest、GraphQLpackages/graphql、NestJS CRUDpackages/nestjsx-crud、Nestjs-Querypackages/nestjs-query、Airtablepackages/airtable、Strapipackages/strapi 与 packages/strapi-v4、Supabasepackages/supabase、Hasurapackages/hasura、Appwritepackages/appwrite、Medusapackages/medusa。使用方式有两种直接npm install [packageName]安装或在项目创建阶段用npm create refine-applatest projectName通过 CLI 交互选择。总结围绕 Data Provider 这一核心抽象Refine v5 提供了从单条读取useOne、列表查询useList/useInfiniteList、增删改useCreate/useUpdate/useDelete到复杂过滤排序分页、meta透传、GraphQL 操作、多 Provider 混用、统一错误契约与关系数据管理的完整方案。其底层由 TanStack Query 驱动自动获得缓存、去重、失效与乐观更新能力。开发者既可以直接选用 packages 目录下的官方 Data Provider也可以参照 contenteditable="false">【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表