十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Wagmi 全栈功能开发指南:从 Core Action 到 Query Options 再到 React/Vue 框架绑定

Wagmi 全栈功能开发指南:从 Core Action 到 Query Options 再到 React/Vue 框架绑定 Wagmi 全栈功能开发指南从 Core Action 到 Query Options 再到 React/Vue 框架绑定【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi本指南系统讲解在 wagmi 仓库中如何新增一个完整的 Wagmi 功能先在三层架构Core Action、Query Options、Framework Bindings中分别落地实现再补充测试、导出与验证。它面向在packages/core、packages/react、packages/vue之间跨层协作的开发者读完后你将掌握新增 action/hook/composable 的标准代码模板、底层调用链config.getClient→getAction→ Viem action以及配套的测试与验证命令。说明本指南覆盖基于 Viem 的 action如getBalance、sendTransaction不涉及 Wagmi 配置类 action如connect、switchChain等。原始技能文档位于 .agents/skills/wagmi-development/SKILL.md下文所有代码模板与其保持一致并以仓库真实源码作为佐证。三层架构总览新增一个 Wagmi 功能时需要在三个层级各写一份代码形成一条从底层到上层的完整调用链Core Actionpackages/core/src/actions/基础功能封装 Viem 的 action负责从Config取 client 并执行链上操作Query Optionspackages/core/src/query/接入 TanStack Query将 action 包装成可被框架层复用的queryOptions/mutationOptionsFramework BindingsReactpackages/react/src/hooks/、Vuepackages/vue/src/composables/把 Query Options 绑定到各框架的响应式体系最终暴露给应用开发者。以getBalance为例仓库中的完整链路为core action → query options → React hook 与 Vue composable其中 React 与 Vue 绑定均从wagmi/core/query导入getBalanceQueryOptions只是响应式封装方式不同。下文按此顺序逐层展开。1. Core Action基础功能封装标准结构模板每个 Core Action 都是对 Viem action 的一层薄封装。下面是技能文档给出的通用模板以myAction占位import { type MyActionErrorType as viem_MyActionErrorType, type MyActionParameters as viem_MyActionParameters, type MyActionReturnType as viem_MyActionReturnType, myAction as viem_myAction, } from viem/actions import type { Config } from ../createConfig.js import type { ChainIdParameter, ConnectorParameter } from ../types/properties.js import type { Compute } from ../types/utils.js import { getAction } from ../utils/getAction.js export type MyActionParametersconfig extends Config Config Compute ChainIdParameterconfig viem_MyActionParameters export type MyActionReturnType viem_MyActionReturnType export type MyActionErrorType viem_MyActionErrorType /** https://wagmi.sh/core/api/actions/myAction */ export async function myActionconfig extends Config( config: config, parameters: MyActionParametersconfig, ): PromiseMyActionReturnType { const { chainId, ...rest } parameters const client config.getClient({ chainId }) const action getAction(client, viem_myAction, myAction) return action(rest) }仓库中的真实实现与模板几乎一一对应例如 getBalance.tsexport async function getBalanceconfig extends Config( config: config, parameters: GetBalanceParametersconfig, ): PromiseGetBalanceReturnType { const { address, blockNumber, blockTag, chainId } parameters const client config.getClient({ chainId }) const action getAction(client, viem_getBalance, getBalance) const value await action( blockNumber ! undefined ? { address, blockNumber } : { address, blockTag }, ) const chain config.chains.find((x) x.id chainId) ?? client.chain! return { decimals: chain.nativeCurrency.decimals, symbol: chain.nativeCurrency.symbol, value, } }注意getBalance在 Viem 返回值之上又补充了decimals、symbol这是 wagmi 层增强返回类型的典型做法——GetBalanceReturnType被重新声明为{ decimals: number; symbol: string; value: bigint }。关键规则技能文档规定了五条必须遵守的编写规则Viem 导入前缀所有从viem/actions导入的符号统一加viem_前缀如viem_getBalance避免与 wagmi 自身的同名 action 冲突也便于在类型与实现中区分来源Client 访问方式只读类 actionconfig.getClient({ chainId })钱包类 actionawait getConnectorClient(config, { chainId, connector, account })混合类 action账号用getConnectorClient只读操作仍用getClient参见 estimateGas.ts参数类型只读 action 必须加ChainIdParameterconfig钱包 action 还要加ConnectorParameter类型参数镜像 Viem 的类型参数以保持类型推断对abi、args等字面量使用const修饰符以获得字面量类型推断展开Spread在调用 Viem action 前先解构掉 wagmi 特有的属性chainId、connector只把剩余参数透传。深入源码getAction的解析优先级模板中的getAction是 wagmi 的核心工具函数packages/core/src/utils/getAction.ts它按三层优先级解析要调用的 action优先取client[actionFn.name]——即 client 上已存在的同名扩展 action例如用户通过 viem 的extend覆盖了sendTransaction实现取client[name]——显式传入的名称对应的 action应对压缩器改写Function.prototype.name的场景因此调用时必须显式传第三个参数兜底直接调用传入的 tree-shakable action 函数actionFn(client, params)。export function getAction...(client, actionFn, name) { const action_implicit client[actionFn.name] if (typeof action_implicit function) return action_implicit const action_explicit client[name] if (typeof action_explicit function) return action_explicit return (params) actionFn(client, params) }这个设计保证了默认走体积更小的 tree-shakable 导入路径但一旦用户在 viem client 上扩展或覆盖了同名 actionwagmi 会自动优先使用覆盖版本实现“可覆盖性”。测试运行时测试action.test.ts使用wagmi/test提供的abi、address、config与 Vitestimport { abi, address, config } from wagmi/test import { expect, test } from vitest import { myAction } from ./myAction.js test(default, async () { await expect(myAction(config, { /* required params */ })).resolves.toMatchInlineSnapshot(...) }) test(parameters: chainId, async () { /* test chainId param */ }) test(behavior: error case, async () { /* test error handling */ })测试约定的命名习惯default默认路径、parameters: xxx参数行为、behavior: xxx边界与错误行为。类型测试action.test-d.ts——仅当 action 存在类型推断时编写import { config } from wagmi/test import { expectTypeOf, test } from vitest import { myAction } from ./myAction.js test(default, async () { const result await myAction(config, { /* params */ }) expectTypeOf(result).toEqualTypeOfExpectedType() })类型基准测试action.bench-d.ts——同样仅在存在类型推断时编写使用ark/attest度量类型实例化开销并做快照断言import { attest } from ark/attest import { test } from vitest import type { MyActionParameters } from ./myAction.js test(default, () { type Result MyActionParameterstypeof abi.erc20, balanceOf const res {} as Result attest.instantiations([12345, instantiations]) attest(res.args).type.toString.snap(readonly [account: \0x\${string}\]) })仓库中真实存在这类基准文件如 multicall.bench-d.ts、readContract.bench-d.ts、writeContract.bench-d.ts。钱包类 action 的测试由于依赖已连接的账号需要在测试内先connect、测试结束后disconnecttest(default, async () { await connect(config, { connector }) await expect(myAction(config, { /* params */ })).resolves.toMatchInlineSnapshot(...) await disconnect(config, { connector }) })混合类 action 的参考实现见 estimateGas.ts当parameters.account缺失时通过getConnectorClient取 connector client 的account再透传给只读 client 上的 ViemestimateGasaction。2. Query Options接入 TanStack Query第二层把第一层的 action 包装为 TanStack Query 的 Query只读或 Mutation钱包操作选项。这一层的产物是框架无关的纯函数myActionQueryOptions/myActionMutationOptionsReact、Vue 绑定都从这里复用。Query 结构import { type MyActionErrorType, type MyActionParameters, type MyActionReturnType, myAction, } from ../actions/myAction.js import type { Config } from ../createConfig.js import type { ScopeKeyParameter } from ../types/properties.js import type { QueryOptions, QueryParameter } from ../types/query.js import type { Compute, ExactPartial } from ../types/utils.js import { filterQueryOptions, structuralSharing } from ./utils.js export type MyActionOptions config extends Config, selectData MyActionData, ComputeExactPartialMyActionParametersconfig ScopeKeyParameter QueryParameterMyActionQueryFnData, MyActionErrorType, selectData, MyActionQueryKeyconfig export function myActionQueryOptions config extends Config, selectData MyActionData, ( config: config, options: MyActionOptionsconfig, selectData {}, ): MyActionQueryOptionsconfig, selectData { return { ...options.query, enabled: Boolean(options.requiredParam (options.query?.enabled ?? true)), queryFn: async (context) { const [, { scopeKey: _, ...parameters }] context.queryKey if (!parameters.requiredParam) throw new Error(requiredParam is required) const result await myAction(config, { ...(parameters as MyActionParameters), requiredParam: parameters.requiredParam, }) return result ?? null }, queryKey: myActionQueryKey(options), structuralSharing, // include when returning complex objects/arrays } } export type MyActionQueryFnData ComputeMyActionReturnType export type MyActionData MyActionQueryFnData export function myActionQueryKeyconfig extends Config( options: ComputeExactPartialMyActionParametersconfig ScopeKeyParameter {}, ) { return [myAction, filterQueryOptions(options)] as const } export type MyActionQueryKeyconfig extends Config ReturnTypetypeof myActionQueryKeyconfig export type MyActionQueryOptions config extends Config, selectData MyActionData, QueryOptionsMyActionQueryFnData, MyActionErrorType, selectData, MyActionQueryKeyconfig真实示例 getBalance.ts 与之同构enabled取决于options.address是否为真queryFn从context.queryKey的第二个元素解构出参数scopeKey被丢弃queryKey为[balance, filterQueryOptions(options)]。Mutation 结构import type { MutationOptions, MutationParameter } from ../types/query.js export type MyActionOptionsconfig extends Config, context unknown MutationParameter MyActionData, MyActionErrorType, MyActionVariablesconfig, context export function myActionMutationOptionsconfig extends Config, context( config: config, options: MyActionOptionsconfig, context {}, ): MyActionMutationOptionsconfig { return { ...options.mutation, mutationFn: async (variables) { return myAction(config, variables) }, mutationKey: [myAction], } } export type MyActionMutationOptionsconfig extends Config MutationOptions MyActionData, MyActionErrorType, MyActionVariablesconfig Mutation 没有enabled/queryKey这类查询概念核心只是把mutationFn绑定到 action。仓库中的 sendTransaction.ts 还额外导出了SendTransactionMutate/SendTransactionMutateAsync类型用于给框架层提供强类型的mutate/mutateAsync签名。关键规则ExactPartial vs UnionExactPartial简单类型用ExactPartial复杂的联合类型如合约类 action用UnionExactPartialenabled基于必填参数是否为真值truthy计算如Boolean(options.address (options.query?.enabled ?? true))structuralSharing当 action 返回对象或数组等复杂结构时引入开启 TanStack Query 的结构共享以复用旧数据引用、减少重渲染filterQueryOptions过滤掉abi、config、connector、query、watch等不可序列化或框架专用的属性避免它们进入 query key对于onReplaced等回调属性需在 query key 中手动跳过Query key统一为[actionName, filterQueryOptions(options)]。深入源码query 工具函数filterQueryOptions与structuralSharing实现在 packages/core/src/query/utils.tsexport function structuralSharingdata(oldData: data | undefined, newData: data): data { return replaceEqualDeep(oldData, newData) }structuralSharing本质是对tanstack/query-core的replaceEqualDeep的包装新旧数据深度比较后若引用可以复用则复用旧引用从而避免不必要的渲染。filterQueryOptions通过解构丢弃两类属性TanStack Query 的查询选项queryFn、queryHash、staleTime、enabled、select、retry、refetchInterval等以及 wagmi 专用属性abi、config、connector、query、watch并把connector替换为可序列化的connectorUidconnector?.uid。这正是 query key 中只保留纯参数的原因——保证 key 稳定且可被序列化缓存。测试import { config } from wagmi/test import { expect, test } from vitest import { myActionQueryOptions } from ./myAction.js test(default, () { expect(myActionQueryOptions(config, {})).toMatchInlineSnapshot( { enabled: false, queryFn: [Function], queryKey: [myAction, {}], } ) }) test(enabled, () { expect(myActionQueryOptions(config, { requiredParam: value }).enabled).toBe(true) }) test(queryFn: calls query fn, async () { const options myActionQueryOptions(config, { requiredParam: value }) const result await options.queryFn({ queryKey: options.queryKey } as any) expect(result).toMatchInlineSnapshot(...) })三个测试分别验证默认快照enabled: false、必填参数驱动enabled、以及queryFn能从 query key 反解参数并正确调用底层 action。3. Framework BindingsReact Hook 与 Vue Composable框架层不重新实现逻辑而是把 Query Options 接入各自的响应式体系。React Query Hookuse client import type { Config, MyActionErrorType, ResolvedRegister } from wagmi/core import type { Compute } from wagmi/core/internal import { type MyActionData, type MyActionOptions, myActionQueryOptions, } from wagmi/core/query import type { ConfigParameter } from ../types/properties.js import { type UseQueryReturnType, useQuery } from ../utils/query.js import { useChainId } from ./useChainId.js import { useConfig } from ./useConfig.js export type UseMyActionParameters config extends Config Config, selectData MyActionData, ComputeMyActionOptionsconfig, selectData ConfigParameterconfig export type UseMyActionReturnTypeselectData MyActionData UseQueryReturnTypeselectData, MyActionErrorType /** https://wagmi.sh/react/api/hooks/useMyAction */ export function useMyAction config extends Config ResolvedRegister[config], selectData MyActionData, ( parameters: UseMyActionParametersconfig, selectData {}, ): UseMyActionReturnTypeselectData { const config useConfig(parameters) const chainId useChainId({ config }) const options myActionQueryOptions(config, { ...parameters, chainId: parameters.chainId ?? chainId, query: parameters.query, }) return useQuery(options) }要点useConfig从 React Context 取全局 configuseChainId提供当前链 ID并在用户未显式传入chainId时作为默认值parameters.chainId ?? chainId。真实实现可对照 useBalance.ts。React Mutation Hookuse client import { useMutation } from tanstack/react-query import type { Config, ResolvedRegister, MyActionErrorType } from wagmi/core import { type MyActionData, type MyActionMutate, type MyActionMutateAsync, type MyActionOptions, type MyActionVariables, myActionMutationOptions, } from wagmi/core/query import type { ConfigParameter } from ../types/properties.js import type { UseMutationReturnType } from ../utils/query.js import { useConfig } from ./useConfig.js export type UseMyActionParametersconfig extends Config Config, context unknown MyActionOptionsconfig, context ConfigParameterconfig export type UseMyActionReturnTypeconfig extends Config Config, context unknown UseMutationReturnType MyActionData, MyActionErrorType, MyActionVariablesconfig, context, MyActionMutateconfig, context, MyActionMutateAsyncconfig, context /** https://wagmi.sh/react/api/hooks/useMyAction */ export function useMyAction config extends Config ResolvedRegister[config], context unknown, ( parameters: UseMyActionParametersconfig, context {}, ): UseMyActionReturnTypeconfig, context { const config useConfig(parameters) const options myActionMutationOptions(config, parameters) const mutation useMutation(options) type Return UseMyActionReturnTypeconfig, context return { ...mutation, mutate: mutation.mutate as Return[mutate], mutateAsync: mutation.mutateAsync as Return[mutateAsync], } }Mutation hook 的关键是重新导出强类型的mutate/mutateAsync。仓库真实示例 useSendTransaction.ts 还保留了向后兼容的sendTransaction/sendTransactionAsync别名标注为deprecated建议改用mutate/mutateAsync并额外导入SendTransactionMutate等类型保证签名的精确推断。Vue ComposableQueryimport type { Config, MyActionErrorType, ResolvedRegister } from wagmi/core import type { Compute } from wagmi/core/internal import { type MyActionData, type MyActionOptions, myActionQueryOptions, } from wagmi/core/query import { computed } from vue import type { ConfigParameter } from ../types/properties.js import type { DeepMaybeRef } from ../types/ref.js import { deepUnref } from ../utils/cloneDeep.js import { type UseQueryReturnType, useQuery } from ../utils/query.js import { useChainId } from ./useChainId.js import { useConfig } from ./useConfig.js export type UseMyActionParameters config extends Config Config, selectData MyActionData, ComputeDeepMaybeRefMyActionOptionsconfig, selectData ConfigParameterconfig export type UseMyActionReturnTypeselectData MyActionData UseQueryReturnTypeselectData, MyActionErrorType /** https://wagmi.sh/vue/api/composables/useMyAction */ export function useMyAction config extends Config ResolvedRegister[config], selectData MyActionData, ( parameters: UseMyActionParametersconfig, selectData {}, ): UseMyActionReturnTypeselectData { const params computed(() deepUnref(parameters)) const config useConfig(params) const chainId useChainId({ config }) const options computed(() myActionQueryOptions(config as any, { ...params.value, chainId: params.value.chainId ?? chainId.value, query: params.value.query, }), ) return useQuery(options as any) as any }Vue 版本与 React 版本的核心差异在于响应式封装参数类型用DeepMaybeRef支持传入 ref运行时通过computed(() deepUnref(parameters))解包所有 ref使参数变化时查询自动响应。真实实现可对照 useBalance.ts。框架规则对比规则ReactVue顶部指令use client无参数包装Compute...ComputeDeepMaybeRef...响应式方式直接使用参数computed()deepUnref()文档位置wagmi.sh/react/api/hooks/wagmi.sh/vue/api/composables/共享规则ResolvedRegister[config]只用在函数签名里作为默认类型参数不要写进类型定义本身Hook 中不出现enabled/structuralSharing这两者已由 Query Options 层处理框架层直接透传即可。关于文档位置仓库内对应的文档目录为 site/react/api/hooks 与 site/vue/api/composables新增功能时应同步补充对应 markdown 文档。测试Query hook 类型测试useMyAction.test-d.ts重点验证select回调与data的类型推断import { abi } from wagmi/test import { expectTypeOf, test } from vitest import { useMyAction } from ./useMyAction.js test(select data, () { const result useMyAction({ /* params */ query: { select(data) { expectTypeOf(data).toEqualTypeOfExpectedDataType() return data }, }, }) expectTypeOf(result.data).toEqualTypeOfExpectedDataType() })Mutation hook 类型测试重点验证onMutate/onError/onSuccess/onSettled回调的variables与context推断import { expectTypeOf, test } from vitest import { useMyAction } from ./useMyAction.js test(context, () { const { mutate } useMyAction({ mutation: { onMutate(variables) { expectTypeOf(variables).toMatchTypeOf{ /* expected shape */ }() return { foo: bar } }, onError(error, variables, context) { /* test types */ }, onSuccess(data, variables, context) { /* test types */ }, onSettled(data, error, variables, context) { /* test types */ }, }, }) mutate({ /* params */ }, { onSuccess(data, variables, context) { /* test inference */ }, }) })注意onMutate返回的context示例中的{ foo: bar }会贯穿到onError、onSuccess、onSettled的第三个参数这正是MutationParameter..., context泛型参数的意义。Exports把新功能暴露出去每一层写完都要同步更新对应包的exports/index.ts将类型与函数导出到公共入口。技能文档给出了三个位置的导出模板// packages/core/src/exports/index.ts export { type MyActionParameters, type MyActionReturnType, type MyActionErrorType, myAction, } from ../actions/myAction.js // packages/core/src/exports/query.ts export { type MyActionData, type MyActionOptions, type MyActionQueryFnData, type MyActionQueryKey, type MyActionQueryOptions, myActionQueryKey, myActionQueryOptions, } from ../query/myAction.js // packages/react/src/exports/index.ts export { type UseMyActionParameters, type UseMyActionReturnType, useMyAction, } from ../hooks/useMyAction.js仓库真实入口文件可见 packages/core/src/exports/index.ts导出全部 actions 与配置相关符号与 packages/core/src/exports/query.ts导出全部 query/mutation options 及hashFn、structuralSharing工具其中每个导出块都同时导出对应类型供消费者做import type使用。Vue 包packages/vue/src/exports/同样维护了自己的导出入口。Verification验证命令清单功能开发完成后用以下命令逐项验证命令在仓库根目录执行使用 pnpm workspace# 格式化 pnpm format # 类型检查全部或按包过滤 pnpm check:types pnpm --filter wagmi/core check:types pnpm --filter wagmi check:types # 测试全部或按项目过滤 pnpm test pnpm test --project core pnpm test --project react # 更新测试快照 pnpm vitest -u # 类型基准测试 pnpm bench:types # 测试快照中 Viem 版本不一致时 pnpm version:update:viem # 构建全部或按包过滤 pnpm run clean pnpm build pnpm --filter wagmi/core build几个命令的用途说明pnpm check:types与pnpm test支持--filter和--project做细粒度过滤迭代期只检查/测试受影响的包可显著加速pnpm vitest -u用于批量更新测试快照上一节的toMatchInlineSnapshot依赖它pnpm bench:types运行*.bench-d.ts类型基准防止新增泛型导致类型实例化开销失控pnpm version:update:viem用于同步 Viem 版本避免测试快照中的类型字符串因版本漂移而失配参见 scripts/updateViemVersion.ts。实战要点总结一条调用链贯穿三层Core Action 负责“取 client 调 Viem”Query Options 负责“包装成 TanStack Query 选项”框架层负责“接入 React/Vue 响应式体系”。新增功能时按 1 → 2 → 3 → Exports → 测试 → 验证的顺序推进即可命名即约定action 用myAction、query 文件同构myActionQueryOptions/myActionQueryKey/myActionMutationOptionshook/composable 用useMyAction便于全仓库保持一致的检索与引用体验类型是硬约束viem_前缀、ChainIdParameter/ConnectorParameter、Compute/ExactPartial/UnionExactPartial、ResolvedRegister[config]的使用位置都是通过test-d.ts类型测试和bench-d.ts基准强制保障的测试分层action 层写运行时 类型 基准测试query 层验证enabled与queryFn行为框架层验证类型推断与回调签名验证命令是交付标准pnpm format、pnpm check:types、pnpm test、pnpm bench:types、pnpm build全部通过才算完成快照失配时用pnpm vitest -u更新。按上述模式为 wagmi 仓库新增功能即可保证新 action 在wagmi/core、wagmi/core/query、wagmi/react、wagmi/vue各层行为一致、类型严谨、可测试可维护与仓库中已有的getBalance、sendTransaction、useBalance等实现保持同等质量水准。【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表