尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Refine v5 Hasura 集成指南:从 GraphQL 数据提供器到 Realtime 订阅的完整实战

Refine v5 Hasura 集成指南:从 GraphQL 数据提供器到 Realtime 订阅的完整实战 Refine v5 Hasura 集成指南从 GraphQL 数据提供器到 Realtime 订阅的完整实战【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refineRefine 官方提供了针对 Hasura一个用于构建和部署 GraphQL API 的平台的专用数据提供器refinedev/hasura。本指南将围绕该集成包系统讲解如何在 Refine v5 应用中接入 Hasura GraphQL 端点、配置 GraphQL Code Generator 获得类型安全体验、使用graphql-tag编写自定义查询与变更、利用内置工具类型简化类型推导以及通过liveProvider开启基于 GraphQL subscription 的实时能力。读完本文你将掌握在 Refine 项目中完整落地 Hasura 数据层与实时层的全部关键技能。包定位与能力概览refinedev/hasura是 Refine 官方维护的 Hasura 集成包源码位于 packages/hasura它在 Refine 的DataProvider与LiveProvider抽象之上把 Hasura 的 GraphQL 约定如_by_pk、_aggregate、_set、_bool_exp等操作与类型命名封装为开箱即用的能力数据层完整实现getOne、getMany、getList、create、createMany、update、updateMany、deleteOne、deleteMany、custom等 Refine 数据方法自动生成对应的 Hasura GraphQL 操作实时层提供基于 GraphQL subscription 的liveProvider支持useList、useOne、useMany三类订阅场景请求层基于graphql-request5处理 HTTP 请求包内同时内置graphql-ws5用于实时 WebSocket 订阅并可直接使用graphql-tag编写查询与变更。从 packages/hasura/package.json 可以看到其依赖与对等依赖graphql-request^5.2.0、graphql-ws^5.9.1、graphql-tag^2.12.6以及用于按 Hasura 约定组装查询的gql-query-builder^3.5.5peerDependencies 要求refinedev/core^5.0.0即面向 Refine v5。若想进一步理解 Refine 的数据获取与实时机制可参考 Data Fetching 指南 与 Realtime 指南。安装使用包安装命令安装集成包安装命令中refinedev/hasura即对应本仓库 packages/hasura 目录所发布的包npm install refinedev/hasura快速上手创建 GraphQL Client 并接入 dataProvider集成包的用法非常直接先用graphql-request的GraphQLClient基于你的 Hasura API 地址创建客户端再把它传给dataProvider(client)工厂函数得到 Refine 可用的数据提供器。import Refine from refinedev/core; import dataProvider, { GraphQLClient } from refinedev/hasura; // 创建 GraphQL 客户端可在此配置 Hasura 的 Header如角色、Admin Secret 等 const client new GraphQLClient(API_URL, { headers: { x-hasura-role: public, }, }); const App () ( Refine dataProvider{dataProvider(client)} {/* ... */} /Refine );其中API_URL形如https://project.hasura.app/v1/graphql。x-hasura-role是 Hasura 基于角色的授权Role-Based Access Control中指定的会话角色这里以public为例实际项目中可根据登录用户动态设置其他 header如x-hasura-admin-secret、x-hasura-user-id等。从 packages/hasura/src/index.ts 可以看到refinedev/hasura的默认导出就是dataProvider同时对外导出GraphQLClient、request、gql、rawRequest、batchRequests等graphql-request的 API以及graphqlWSgraphql-ws命名空间方便你在同一处导入请求层工具。数据提供器的可选配置dataProvider工厂函数还接受第二个参数options类型定义见 packages/hasura/src/types/index.ts选项类型默认值说明idTypeuuid \| Int \| String \| Numeric或(resource: string) IDTypeuuid主键字段的 GraphQL 标量类型某些资源使用非uuid主键时可通过函数按资源区分namingConventionhasura-default \| graphql-defaulthasura-defaultHasura 的字段命名约定graphql-default对应 Hasura 开启 GraphQL default naming convention即驼峰命名时的模式例如dataProvider(client, { idType: (resource) (resource posts ? Int : uuid) })可让posts资源使用Int主键。从 packages/hasura/src/dataProvider/index.ts 的实现看namingConvention会决定数据提供器在拼接操作名如${resource}_by_pk、insert_${operation}_one、update_${operation}_by_pk以及过滤/排序变量名where、order_byvsorderBy时是否进行驼峰转换。深入数据提供器分页、排序、过滤与 CRUD 的底层实现getList偏移分页 聚合计数Refine 的useList/useTable最终会调用数据提供器的getList。packages/hasura/src/dataProvider/index.ts 中的实现要点分页读取pagination默认{ currentPage: 1, pageSize: 10, mode: server }在mode server时换算为 Hasura 的limit与offset: (currentPage - 1) * limitmode为off/client时则不生成分页变量排序通过generateSorting(sorters)把 Refine 的CrudSorting形如[{ field: category.title, order: asc }]转换为 Hasura 的嵌套order_by对象过滤通过generateFilters(filters, namingConvention)把 Refine 的 Crud 过滤器转换为 Hasura 的whereBoolExp计数同时发起${operation}_aggregate查询取aggregate.count作为返回的total供表格分页器使用。过滤操作符映射packages/hasura/src/utils/generateFilters.ts 中定义了 Refine Crud 操作符到 Hasura 操作符的完整映射并支持field.split(.)处理嵌套字段如category.titleRefine 操作符Hasura 操作符说明eq/ne_eq/_neq等于 / 不等于lt/gt/lte/gte_lt/_gt/_lte/_gte比较运算in/nin_in/_nin在集合内 / 不在集合内contains/ncontains_ilike/_nilike大小写不敏感包含值包装为%value%containss/ncontainss_like/_nlike大小写敏感包含值包装为%value%startswith/endswith_iregex以^value/value$形式匹配正则startswiths/endswiths_similar以value%/%value形式匹配SIMILAR TOnull/nnull_is_null值为true/falseor/and/not_or/_and/_not布尔组合支持任意嵌套各 CRUD 方法生成的 Hasura 操作数据提供器为每个 Refine 方法按约定生成 Hasura 操作hasura-default命名下Refine 方法生成的 Hasura 操作关键输入getOne${resource}_by_pk(id: $id)idgetMany${resource}(where: { id: { _in: ids } })idsgetList${resource}(offset, limit, order_by, where)${resource}_aggregate(where)pagination、sorters、filterscreateinsert_${resource}_one(object: $object)variablescreateManyinsert_${resource}(objects: $objects) { returning { id ... } }variables数组updateupdate_${resource}_by_pk(pk_columns: { id }, _set: $object)id、variablesupdateManyupdate_${resource}(where: { id: { _in: ids } }, _set: $object)ids、variablesdeleteOnedelete_${resource}_by_pk(id: $id)iddeleteManydelete_${resource}(where: { id: { _in: ids } })ids值得注意的两个实现细节getApiUrl方法在 hasura 数据提供器中会抛出getApiUrl method is not implemented on refine-hasura data provider.说明该集成不支持此方法custom方法允许你通过meta.operation、meta.fields、meta.variables直接发起任意 GraphQL 查询或变更method get走查询、否则走变更也可传入url与headers动态创建客户端。开发者体验GraphQL Code Generator 配置官方建议使用GraphQL Code Generator为查询和变更生成 TypeScript 类型从而获得自动补全与编译期类型检查。npm i -D graphql-codegen/cli5 graphql-codegen/typescript4 graphql-codegen/import-types-preset3在项目根目录添加graphql.config.tsimport type { IGraphQLConfig } from graphql-config; const config: IGraphQLConfig { schema: https://flowing-mammal-24.hasura.app/v1/graphql, extensions: { codegen: { // 可选用于格式化生成的文件 hooks: { afterOneFileWrite: [eslint --fix, prettier --write], }, generates: { src/graphql/schema.types.ts: { plugins: [typescript], config: { skipTypename: true, enumsAsTypes: true, }, }, src/graphql/types.ts: { preset: import-types, documents: [src/**/*.{ts,tsx}], plugins: [typescript-operations], config: { skipTypename: true, enumsAsTypes: true, preResolveTypes: false, useTypeImports: true, }, presetConfig: { typesPath: ./schema.types, }, }, }, }, }, }; export default config;在package.json中添加脚本{ scripts: { codegen: graphql-codegen --config ./graphql.config.ts } }然后运行npm run codegen它将生成两个文件src/graphql/schema.types.ts整个 GraphQL schema 的类型定义src/graphql/types.ts基于你项目中documents即src/**/*.{ts,tsx}中的gql片段生成的查询与变更类型。同时建议为编辑器安装 GraphQL Language Service如 VSCode 的 GraphQL 扩展配合graphql.config.ts获得 schema 感知的补全与校验。实际示例可参考仓库中的 examples/data-provider-hasura其中包含了完整的graphql.config.ts、vite.config.ts与src目录结构可直接对照学习。使用graphql-tag编写自定义查询与变更Refine hooks 的meta对象提供了可选的gqlQuery与gqlMutation属性用于注入用graphql-tag编写好的查询/变更文档。最佳实践是将查询和变更单独放在组件旁边的文件里。import gql from graphql-tag; export const POSTS_LIST_QUERY gql query PostsList( $offset: Int! $limit: Int! $order_by: [posts_order_by!] $where: posts_bool_exp ) { posts(offset: $offset, limit: $limit, order_by: $order_by, where: $where) { id title content created_at category { id title } } posts_aggregate(where: $where) { aggregate { count } } } ; export const POST_EDIT_MUTATION gql mutation PostEdit($id: uuid!, $object: posts_set_input!) { update_posts_by_pk(pk_columns: { id: $id }, _set: $object) { id title content category_id category { id title } } } ;注意自定义查询需要显式声明$offset、$limit、$order_by、$where变量——这些正是数据提供器在getList中会注入的变量名参见上文实现hasuraPagination、order_by、where会被合并进请求变量。编写完查询后再次运行npm run codegen生成对应类型然后即可在 hooks 中使用import { useList, useTable, useForm } from refinedev/core; import { GetFields, GetFieldsFromList, GetVariables } from refinedev/hasura; import { PostsListQuery, PostEditMutation } from src/graphql/types; import { POSTS_LIST_QUERY, POST_EDIT_MUTATION } from ./queries; const { result, query } useListGetFieldsFromListPostsListQuery({ meta: { gqlQuery: POSTS_LIST_QUERY }, }); const { tableProps } useTableGetFieldsFromListPostsListQuery({ meta: { gqlQuery: POSTS_LIST_QUERY }, }); const { formProps } useForm GetFieldsPostEditMutation, HttpError, GetVariablesPostEditVariables ({ meta: { gqlMutation: POST_EDIT_MUTATION }, });useForm的自动getOne派生在上面的useForm示例中只传了gqlMutation而没有单独传gqlQuery初始化时useForm会调用getOne获取表单初始值此时refinedev/hasura会自动检测gqlMutation、从中提取已选择的字段并据此构造一次getOne查询。这一行为可在 packages/hasura/src/dataProvider/index.ts 中验证getOne会优先读取meta?.gqlQuery ?? meta?.gqlMutation若拿到的是变更文档isMutation(gqlOperation)为真则通过getOperationFields提取其选择集重新包装为query Get${Operation}($id: ${idType}!) { ${operation}(id: $id) { ...fields } }执行。如果你希望自定义getOne的查询形态例如需要更多字段、调整返回结构可以同时传入gqlQueryconst POST_EDIT_QUERY gql query PostEdit($id: uuid!) { blogPost(id: $id) { id title status category { id title } categoryId content } } ; const { formProps } useFormGetFieldsPostEditMutation({ meta: { gqlMutation: POST_EDIT_MUTATION, gqlQuery: POST_EDIT_QUERY, }, });提示meta.gqlVariables可额外注入自定义查询所需的变量如where数据提供器会在getList、getMany、deleteMany等场景下通过mergeHasuraFilters将gqlVariables.where与自动生成的过滤条件合并见 generateFilters.ts。内置工具类型GetFields / GetFieldsFromList / GetVariablesrefinedev/hasura额外导出 3 个工具类型实现见 packages/hasura/src/interfaces.ts用于从 Code Generator 生成的查询/变更类型中提取选择集类型避免手动书写字段类型。GetFields针对单条记录的查询或变更选择集默认包裹在操作名如posts_by_pk、insert_posts_one之下GetFields可把它拍平query PostShow($id: uuid!) { posts_by_pk(id: $id) { id } } mutation PostCreate($object: posts_insert_input!) { insert_posts_one(object: $object) { id } }import { GetFields } from refinedev/hasura; import { PostShowQuery, PostCreateMutation } from src/graphql/types; PostShowQuery; // { posts_by_pk: { id: string }; } GetFieldsPostShowQuery; // { id: string; } PostCreateMutation; // { insert_posts_one: { id: string; } } GetFieldsPostCreateMutation; // { id: string; }GetFieldsFromList列表查询的选择集位于posts数组与posts_aggregate计数之下且数据提供器已返回归一化结果datatotal因此直接用生成的类型并不方便。GetFieldsFromList会把列表元素类型提取出来query PostsList( $offset: Int! $limit: Int! $order_by: [posts_order_by!] $where: posts_bool_exp ) { posts(offset: $offset, limit: $limit, order_by: $order_by, where: $where) { id posts_aggregate(where: $where) { aggregate { count } } } }export type PostsListQuery { posts: Array Pick Types.Posts, id | title | content | category_id | created_at { category?: Types.MaybePickTypes.Categories, id | title; } ; posts_aggregate: { aggregate?: Types.MaybePickTypes.Posts_Aggregate_Fields, count; }; };import { GetFieldsFromList } from refinedev/hasura; type PostFields GetFieldsFromListPostsListQuery; PostFields; // { id: string, total: number }上述id/total为示意——total由数据提供器从posts_aggregate.aggregate.count归一化而来。GetVariables对于以$object为变量的变更如PostCreateGetVariables会提取object字段对应的类型mutation PostCreate($object: posts_insert_input!) { insert_posts_one(object: $object) { id } }export type PostCreateVariables Types.Exact{ object: Types.Posts_Insert_Input; };import { GetVariables } from refinedev/hasura; type PostCreateVariables GetVariablesPostCreateVariables; PostCreateVariables; // { title: string; content: string; }其实现本质为GetFieldsT, object即取类型T的object键对应对$object变量的定义再拍平其字段。Realtime用 liveProvider 开启实时订阅refinedev/hasura同时导出一个liveProvider用于启用 Refine 的实时特性。它基于 GraphQL subscription并使用graphql-ws处理 WebSocket 连接。import Refine from refinedev/core; import dataProvider, { GraphQLClient, liveProvider, graphqlWS, } from refinedev/hasura; const client new GraphQLClient(API_URL, { headers: { x-hasura-role: public, }, }); const wsClient graphqlWS.createClient({ url: WS_URL, }); const App () ( Refine dataProvider{dataProvider(client)} liveProvider{liveProvider(wsClient)} options{{ liveMode: auto }} {/* ... */} /Refine );其中WS_URL是 Hasura GraphQL 的 WebSocket 端点如wss://project.hasura.app/v1/graphql。liveMode: auto会让 Refine 在数据变更时自动重取/订阅相关查询关于实时特性的更多用法如useSubscription、liveMode的manual模式、liveParams的subscriptionType等可参见 Realtime 指南。从 packages/hasura/src/liveProvider/index.ts 的实现看subscribe根据params.subscriptionTypeuseList、useOne、useMany之一选择对应的订阅生成器generateUseListSubscription、generateUseOneSubscription、generateUseManySubscription源码位于 packages/hasura/src/utils生成器会结合resource、filters、sorters、pagination、id/ids、meta与命名约定构造对应的 subscription 文档与变量params.meta、params.subscriptionType、params.resource均为必填缺失时会抛出带[useSubscription]前缀的错误提示建立连接后每次收到推送数据callback(payload.data[operation])会被调用把 subscription 返回的选择集数据交给 Refine 的 live 机制分发。同样该数据提供器也支持namingConvention与idType选项liveProvider(wsClient, options)确保 subscription 生成的类型名与数据层保持一致。动手实践建议对照示例运行仓库中的 examples/data-provider-hasura 是一个可直接运行的完整示例基于 Vite包含graphql.config.ts、示例页面与类型生成配置可作为上手脚手架先跑通基础 CRUD用默认dataProvider(client)接入后配合useList/useTable/useForm完成列表、表单、删除等基础操作再逐步引入自定义gqlQuery/gqlMutation再接入类型生成配置 Code Generator 与graphql.config.ts把useForm的泛型替换为GetFields...、GetVariables...让编译器帮你校验字段拼写与变量类型最后开启实时按需配置liveProvider与liveMode: auto体验数据变更自动同步到界面的效果注意 WebSocket 端点的协议前缀为wss://留意命名约定如果你的 Hasura 项目开启了 GraphQL 默认命名约定驼峰务必把namingConvention设为graphql-default否则生成的操作名与变量名会不匹配。结语refinedev/hasura用约千行代码把 Hasura 的 GraphQL 约定完整翻译成了 Refine 的DataProvider与LiveProvider抽象开发者既可以零配置享受自动生成的 CRUD 查询也可以通过meta.gqlQuery/gqlMutation精确控制每一个 GraphQL 文档再借助GetFields/GetFieldsFromList/GetVariables三个工具类型把类型安全贯穿到 hooks 层。配合 GraphQL Code Generator 与graphql-ws订阅它足以支撑从简单后台到复杂实时管理面板的完整数据层需求。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表