wagmi getClient Action 详解:获取 Viem Client 实例的完整指南
getClient 是 wagmi Core 提供的核心 Action,用于获取当前配置(Config)对应的 Viem Client 实例。在本仓库(wagmi monorepo)中,它是所有底层链上操作(读合约、发交易、查余额、估 Gas 等)的公共起点,正确理解它的参数、返回类型与底层缓存机制,能帮助你写出类型安全且高效的应用代码。读完本文,你将掌握 getClient 的导入方式、chainId 参数的精确语义、返回值类型的推导规则,以及它在 createConfig 内部如何按链缓存 Client 的实现原理。
什么是 getClient
在 wagmi 的架构中,Config(由 createConfig 创建)持有全局状态(当前链、连接、连接器)以及每个链对应的 Viem Client。getClient 就是把这个 Client 取出来的标准入口:
import { getClient } from '@wagmi/core'
它位于 @wagmi/core 包的 actions 目录下,核心实现见 getClient.ts。与之配套的还有 React 框架层的 useClient Hook(见 useClient.ts),适合在组件内响应式地获取 Client。
基础用法
getClient 的第一个参数是 config,即由 createConfig 创建并通常在各处导出的全局配置对象。最简单的调用如下:
// index.ts
import { getClient } from '@wagmi/core'
import { config } from './config'
const client = getClient(config)
配套的配置示例(本仓库文档使用的标准写法)位于 config.ts:
// config.ts
import { createConfig, http } from '@wagmi/core'
import { mainnet, sepolia } from '@wagmi/core/chains'
export const config = createConfig({
chains: [mainnet, sepolia],
transports: {
[mainnet.id]: http(),
[sepolia.id]: http(),
},
})
上面的 config 配置了 mainnet 与 sepolia 两条链,并为每条链注册了 http() 传输层。未传 chainId 时,getClient(config) 返回的是当前链(config.state.chainId,默认是 chains 数组中的第一条链)对应的 Client。
参数详解
chainId
类型:config['chains'][number]['id'] | undefined
chainId 用于指定要获取哪个链的 Viem Client。若不传,则使用配置中当前的链 ID。当需要跨链操作(例如在主链上读余额、在测试网上发交易)时,显式传入 chainId 非常关键:
// index.ts
import { getClient } from '@wagmi/core'
import { mainnet } from '@wagmi/core/chains'
import { config } from './config'
const client = await getClient(config, {
chainId: mainnet.id, // [!code focus]
})
注意示例中使用了 await——实际上 getClient 是同步函数,这里写成 await 也完全合法(对同步返回值取 await 会直接得到该值),但不传 chainId 时用法一致,无需异步等待。
从源码类型定义看,GetClientParameters 基于 ChainIdParameter 泛型约束实现(见 properties.ts):
export type ChainIdParameter<
config extends Config,
chainId extends
| config['chains'][number]['id']
| undefined = config['chains'][number]['id'],
> = {
chainId?:
| (chainId extends config['chains'][number]['id'] ? chainId : undefined)
| config['chains'][number]['id']
| undefined
}
这意味着 TypeScript 会强制 chainId 必须是 config.chains 中已配置的链 ID。若传入未配置的链 ID(例如 123456),类型系统会直接报错,且在运行时返回 undefined(详见下文"行为边界")。
其他参数
getClient 仅接受 config 与 chainId 两个输入。它不像 getBalance、call 等 Action 那样需要额外的 address、abi 等业务参数——它只是 Client 的取用入口。
返回值类型
getClient 的返回值类型为 GetClientReturnType,可导入自 @wagmi/core:
import { type GetClientReturnType } from '@wagmi/core'
正常情况下返回类型为 Viem Client 实例。但注意该返回类型是条件类型:当传入的 chainId 属于已配置链时,返回对应链的精确 Client<transport, chain> 类型;当链未配置(不满足约束)时,返回类型收窄为 undefined。
在 getClient.test-d.ts 的类型测试中可以看到这一推导的实际效果:
// 默认场景:返回类型与 config.chains 元素一致,transport 为 'http'
const client = getClient(config)
expectTypeOf(client.chain).toEqualTypeOf<(typeof config)['chains'][number]>()
expectTypeOf(client.transport.type).toEqualTypeOf<'http'>()
// 指定 chainId:返回类型精确到该链
const client = getClient(config, { chainId: chain.mainnet.id })
expectTypeOf(client.chain).toEqualTypeOf<typeof chain.mainnet>()
expectTypeOf(client.transport.type).toEqualTypeOf<'http'>()
// 未配置的链:返回类型为 undefined
const client = getClient(config, { chainId: 123456 })
expectTypeOf(client).toEqualTypeOf<undefined>()
也就是说,只要你的 chains 与 transports 配置正确,从 getClient 取出的 Client 天然携带精确的链类型与传输层类型,后续传给 viem 的只读/写操作时无需任何类型断言。
底层实现与缓存机制
getClient Action 本身只是一个轻量封装(见 getClient.ts 第 40-52 行):
export function getClient<
config extends Config,
chainId extends config['chains'][number]['id'] | number | undefined,
>(
config: config,
parameters: GetClientParameters<config, chainId> = {},
): GetClientReturnType<config, chainId> {
try {
return config.getClient(parameters) as GetClientReturnType<config, chainId>
} catch {
return undefined as never
}
}
关键逻辑在 config.getClient 中,其实现位于 createConfig.ts,核心流程如下:
- 解析目标链:
chainId = parameters.chainId ?? store.getState().chainId,即显式传参优先,否则使用 store 中的当前链。 - 校验链是否配置:在
chains列表中查找该chainId;若显式指定了chainId但链未配置,抛出ChainNotConfiguredError(错误类定义见 config.ts)。getClientAction 的try/catch正是把这种异常吞掉并返回undefined。 - 兜底当前链:如果目标链未配置(未显式指定
chainId时),则回退返回当前链的 Client。 - 内存缓存(memoization):
clients是一个Map<number, Client>,以链 ID 为键缓存已创建的 Client。命中缓存直接返回,避免重复构建。 - 懒创建:缓存未命中时,如果
createConfig传入了自定义client工厂函数,则调用rest.client({ chain });否则基于transports与继承的 viem Client 配置(如batch、pollingInterval等)调用 viem 的createClient创建新 Client,然后写入缓存。
从源码结构可以看出两个重要设计:
- 按链隔离与共享:每条链持有独立 Client,但同一条链的所有调用共享同一个实例(通过缓存),避免重复创建带来的资源浪费;
- 默认开启 batch:当未显式配置
batch时,createClient使用batch: { multicall: true }(createConfig.ts),让 viem 默认聚合多个调用。
行为边界:未配置链返回 undefined
这是 getClient 最容易踩坑的一点:传入未配置的 chainId 时,getClient 返回 undefined 而不是抛出异常。这一点由 getClient.test.ts 直接验证:
test('default', () => {
expect(getClient(config)).toBeDefined()
})
test('behavior: unconfigured chain', () => {
expect(
getClient(config, {
// @ts-expect-error
chainId: 123456,
}),
).toBeUndefined()
})
因此在使用返回值前建议做判空处理,或将 chainId 限定在配置链集合内(类型系统会帮助你)。
在生态中的位置:众多 Action 的公共入口
getClient 的价值不仅在于直接使用,更在于它是 wagmi 几乎所有链上 Action 的底层依赖。从源码搜索可以看到,大量 Action 在内部通过 config.getClient({ chainId }) 获取 Client 后再调用 viem,例如:
- getBalance.ts:查询账户余额
- call.ts:执行只读调用
- deployContract.ts:部署合约
- estimateGas.ts:预估 Gas
- getBlock.ts:读取区块信息
这也解释了为什么 getClient 是构建自定义链上逻辑的基石:当内置 Action 无法覆盖你的需求时,可以先用 getClient(config, { chainId }) 拿到精确类型的 Client,再直接调用 viem 的任意功能。
React 场景:使用 useClient Hook
在 React 应用中,更推荐通过框架层 Hook 获取 Client。useClient(useClient.ts)会自动从 WagmiProvider 注入的 config 中取值,并随当前链变化响应式更新,示例:
import { useClient } from 'wagmi'
function Component() {
const client = useClient()
// client 即为当前链的 Viem Client
}
它同样支持传入 chainId 参数。对于需要在组件外(事件回调、服务端逻辑)获取 Client 的场景,则使用本文的 getClient。
实践建议
- 优先复用
config:getClient要求显式传入config,请确保全局只创建一份配置并统一导出,避免多次createConfig导致状态与缓存分裂。 - 善用
chainId实现多链逻辑:需要操作非当前链时,显式传入目标链 ID;同时利用类型约束,让编译器帮你拦截"链未配置"的低级错误。 - 注意
undefined判空:在动态chainId(如来自用户输入)场景下,对getClient的返回值做防御性判断。 - 深入阅读:完整的配置创建逻辑见 createConfig.ts,类型定义见 properties.ts,行为测试见 getClient.test.ts 与 getClient.test-d.ts,框架集成示例可参考 config.ts。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



