useEnsAvatar:在 wagmi 中获取 ENS 头像的完整指南
【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi
useEnsAvatar是 wagmi 提供的 React Hook,用于根据 ENS 名称(ENS name)获取对应的头像(avatar)URL。本文以 wagmi 仓库中的官方文档 site/react/api/hooks/useEnsAvatar.md 为主体,结合 hooks 源码、core action 实现 与 测试用例 展开讲解,覆盖导入方式、基本用法、全部参数含义、查询选项、返回类型,以及底层查询键与缓存机制,帮助你快速在 React 应用中实现 ENS 头像展示。
导入 Hook
在 React 组件中,通过以下方式从wagmi导入useEnsAvatar:
import { useEnsAvatar } from 'wagmi'useEnsAvatar基于 TanStack Query 实现,返回的是一个查询结果对象(而非直接的字符串),因此可以自然融入WagmiProvider+createConfig的响应式数据流中。
基本用法
下面是一个最小示例,通过viem/ens提供的normalize函数将 ENS 名称规范化后传入 Hook:
import { useEnsAvatar } from 'wagmi' import { normalize } from 'viem/ens' function App() { const result = useEnsAvatar({ name: normalize('wevm.eth'), }) }配套的配置如下(示例配置片段来自 site/snippets/react/config.ts):
import { createConfig, http } from 'wagmi' import { mainnet, sepolia } from 'wagmi/chains' export const config = createConfig({ chains: [mainnet, sepolia], transports: { [mainnet.id]: http(), [sepolia.id]: http(), }, })为什么必须先
normalize?ENS 名称禁止某些特殊字符(如下划线_),且存在其他校验规则,因此在传给useEnsAvatar之前,建议先使用 UTS-46 规范化 处理名称。Viem 内置的normalize函数正是为此设计,可避免因名称未规范化导致的解析失败。
参数(Parameters)
useEnsAvatar接受一个UseEnsAvatarParameters类型的参数对象,类型定义如下:
import { type UseEnsAvatarParameters } from 'wagmi'从源码看,该类型是GetEnsAvatarOptions<config, selectData> & ConfigParameter<config>的合并结果(见 packages/react/src/hooks/useEnsAvatar.ts#L17-L20),即同时包含查询参数与 wagmi 配置参数。
assetGatewayUrls
- 类型:
{ ipfs?: string | undefined; arweave?: string | undefined } | undefined - 说明:用于解析 IPFS 和/或 Arweave 资产的自定义网关 URL。当头像指向 IPFS 或 Arweave 上的资源时,可通过该参数指定可访问的网关。
- 版本要求:
viem@>=2.3.1
import { getEnsAvatar } from '@wagmi/core' import { normalize } from 'viem/ens' import { config } from './config' function App() { const result = useEnsAvatar({ assetGatewayUrls: { ipfs: 'https://cloudflare-ipfs.com', }, name: normalize('wevm.eth'), }) }blockNumber
- 类型:
bigint | undefined - 说明:指定在某个区块高度(block number)上获取 ENS 头像,用于查询历史状态的头像。
import { useEnsAvatar } from 'wagmi' import { normalize } from 'viem/ens' function App() { const result = useEnsAvatar({ blockNumber: 17829139n, name: normalize('wevm.eth'), }) }blockTag
- 类型:
'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined - 说明:指定获取头像时使用的区块标签(block tag),默认通常为
'latest'。
import { useEnsAvatar } from 'wagmi' import { normalize } from 'viem/ens' function App() { const result = useEnsAvatar({ name: normalize('wevm.eth'), blockTag: 'latest', }) }chainId
- 类型:
config['chains'][number]['id'] | undefined - 说明:指定执行查询的链 ID。未指定时,Hook 会通过
useChainId自动使用当前激活的链。
import { useEnsAvatar } from 'wagmi' import { mainnet } from 'wagmi/chains' import { normalize } from 'viem/ens' function App() { const result = useEnsAvatar({ chainId: mainnet.id, name: normalize('wevm.eth'), }) }config
- 类型:
Config | undefined - 说明:显式传入
Config以替代从最近的WagmiProvider中读取的配置。适用于脱离 Provider 上下文或需要覆盖配置的场景。
import { useEnsAvatar } from 'wagmi' import { normalize } from 'viem/ens' import { config } from './config' function App() { const result = useEnsAvatar({ config, name: normalize('wevm.eth'), }) }gatewayUrls
- 类型:
string[] | undefined - 说明:一组 Universal Resolver 网关地址,用于解析通过 ENS Universal Resolver 合约发起的 CCIP-Read 请求。
import { useEnsAvatar } from 'wagmi' import { normalize } from 'viem/ens' function App() { const result = useEnsAvatar({ gatewayUrls: ['https://cloudflare-ipfs.com'], name: normalize('wevm.eth'), }) }name
- 类型:
string | undefined - 说明:要获取头像的 ENS 名称。当
name为undefined时,enabled会被自动置为false,查询不会执行(该逻辑见下文源码剖析部分)。
import { useEnsAvatar } from 'wagmi' import { normalize } from 'viem/ens' function App() { const result = useEnsAvatar({ name: normalize('wevm.eth'), }) }scopeKey
- 类型:
string | undefined - 说明:将缓存限定在给定上下文范围内。具有相同
scopeKey的 Hook 会共享同一份缓存,适用于需要在不同场景下隔离或复用缓存数据的情形。
import { useEnsAvatar } from 'wagmi' import { normalize } from 'viem/ens' function App() { const result = useEnsAvatar({ name: normalize('wevm.eth'), scopeKey: 'foo', }) }universalResolverAddress
- 类型:
Address | undefined - 说明:
- ENS Universal Resolver 合约地址;
- 未指定时,默认使用当前链的 Universal Resolver 合约地址。
import { useEnsAvatar } from 'wagmi' import { normalize } from 'viem/ens' function App() { const result = useEnsAvatar({ name: normalize('wevm.eth'), universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376', }) }查询选项(query)
useEnsAvatar透传 TanStack Query 的查询选项(详见 site/shared/query-options.md)。需要特别说明:wagmi 不允许覆盖所有 TanStack Query 参数,其中queryFn与queryKey由 wagmi 内部使用,不可自定义;下表所列参数均受支持。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | boolean \| undefined | — | 设为false可禁用查询自动执行,常用于依赖查询(Dependent Queries) |
gcTime | number \| Infinity \| undefined | 5 * 60 * 1000(5 分钟),SSR 期间为Infinity | 未使用/非活跃缓存数据的保留时长,Infinity表示禁用垃圾回收 |
initialData | TData \| (() => TData) \| undefined | — | 作为查询初始缓存数据;函数形式仅会在共享/根查询初始化时调用一次;默认视为已过期(除非设置了staleTime);会被持久化到缓存 |
initialDataUpdatedAt | number \| (() => number \| undefined) \| undefined | — | initialData本身的最后更新时间(毫秒) |
meta | Record<string, unknown> \| undefined | — | 附加到查询缓存条目上的额外信息,可在queryFn的QueryFunctionContext中访问 |
networkMode | 'online' \| 'always' \| 'offlineFirst' \| undefined | 'online' | 网络模式 |
notifyOnChangeProps | string[] \| 'all' \| (() => string[] \| 'all') \| undefined | 默认按属性访问跟踪 | 仅当列出的属性变化时触发组件重渲染 |
placeholderData | TData \| ((previousValue, previousQuery) => TData) \| undefined | — | 查询处于pending状态时使用的占位数据;不会持久化到缓存 |
queryClient | QueryClient \| undefined | 最近上下文中的实例 | 指定自定义QueryClient |
refetchInterval | number \| false \| ((data, query) => number \| false \| undefined) \| undefined | — | 定时自动重新拉取的毫秒间隔 |
refetchIntervalInBackground | boolean \| undefined | — | true时,后台标签页也会继续定时重拉取 |
refetchOnMount | boolean \| 'always' \| ((query) => boolean \| 'always') \| undefined | true | 挂载时若数据已过期则重新拉取 |
refetchOnReconnect | boolean \| 'always' \| ((query) => boolean \| 'always') \| undefined | true | 网络重连时若数据已过期则重新拉取 |
refetchOnWindowFocus | boolean \| 'always' \| ((query) => boolean \| 'always') \| undefined | true | 窗口重新聚焦时若数据已过期则重新拉取 |
retry | boolean \| number \| ((failureCount, error) => boolean) \| undefined | 客户端3,服务端0 | 失败重试策略 |
retryDelay | number \| ((retryAttempt, error) => number) \| undefined | — | 每次重试前的延迟毫秒数,可配合指数退避函数 |
retryOnMount | boolean \| undefined | true | 挂载时若查询含错误是否重试 |
select | ((data: TData) => unknown) \| undefined | — | 转换/选取查询数据,只影响返回值data,不影响缓存内容 |
staleTime | number \| Infinity \| undefined | 0 | 数据过期时间(毫秒),Infinity表示永不过期 |
structuralSharing | boolean \| ((oldData, newData) => TData) \| undefined | true | 是否启用查询结果间的结构共享 |
注意:对
useEnsAvatar而言,TData = string | null,TError = GetEnsAvatarErrorType。
返回类型(Return Type)
import { type UseEnsAvatarReturnType } from 'wagmi'返回类型UseEnsAvatarReturnType实为UseQueryReturnType<string | null, GetEnsAvatarErrorType>(见 packages/react/src/hooks/useEnsAvatar.ts#L22-L23),即标准 TanStack Query 结果(详见 site/shared/query-result.md)。核心字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
data | string \| null | 最近一次成功解析的头像 URL,默认undefined |
dataUpdatedAt | number | 查询最近一次返回'success'状态的时间戳 |
error | null \| GetEnsAvatarErrorType | 查询抛出的错误对象,默认null |
errorUpdatedAt | number | 最近一次进入'error'状态的时间戳 |
errorUpdateCount | number | 累计错误次数 |
failureCount | number | 失败计数,成功时重置为0 |
failureReason | null \| GetEnsAvatarErrorType | 重试失败的原因,成功时重置为null |
fetchStatus | 'fetching' \| 'idle' \| 'paused' | 查询拉取状态 |
isError/isPending/isSuccess | boolean | 由status派生的布尔标记 |
isFetched | boolean | 查询是否已完成过拉取 |
isFetchedAfterMount | boolean | 组件挂载后是否拉取过(可用于隐藏旧缓存) |
isFetching/isPaused | boolean | 由fetchStatus派生的布尔标记 |
isLoading | boolean | 首次拉取进行中,等价于isFetching && isPending |
isLoadingError | boolean | 首次拉取是否失败 |
isPlaceholderData | boolean | 当前展示的是否为占位数据 |
isRefetchError | boolean | 后台重拉取是否失败 |
isRefetching | boolean | 后台重拉取进行中,等价于isFetching && !isPending |
isStale | boolean | 缓存数据是否已过期 |
refetch | (options) => Promise<...> | 手动重新拉取;throwOnError控制失败时抛错还是仅记录日志,cancelRefetch控制是否取消正在运行的请求,默认true |
status | 'error' \| 'pending' \| 'success' | 查询整体状态 |
在组件中通常会这样使用:
const { data: avatar, isPending, isError, error } = useEnsAvatar({ name: normalize('wevm.eth'), }) if (isPending) return <div>Loading avatar…</div> if (isError) return <div>Error: {error.message}</div> return avatar ? <img src={avatar} alt="ENS avatar" /> : nullTanStack Query 编程式 API
如果希望脱离 React Hook,使用底层命令式 API(例如在事件处理函数中手动触发查询),可以从wagmi/query导入对应类型与工具(详见 site/shared/query-imports.md):
import { type GetEnsAvatarData, type GetEnsAvatarOptions, type GetEnsAvatarQueryFnData, type GetEnsAvatarQueryKey, getEnsAvatarQueryKey, getEnsAvatarQueryOptions, } from 'wagmi/query'Action:getEnsAvatar
useEnsAvatar底层委托给getEnsAvataraction。核心实现位于 packages/core/src/actions/getEnsAvatar.ts:
export function getEnsAvatar<config extends Config>( config: config, parameters: GetEnsAvatarParameters<config>, ): Promise<GetEnsAvatarReturnType> { const { chainId, ...rest } = parameters const client = config.getClient({ chainId }) const action = getAction(client, viem_getEnsAvatar, 'getEnsAvatar') return action(rest) }调用链可概括为:useEnsAvatar(React Hook)→getEnsAvatarQueryOptions(查询选项)→getEnsAvatar(core action)→viem的getEnsAvatar(viem/actions导入)→ 链上 Universal Resolver 合约解析。chainId参数被单独取出并用于从config获取对应链的 viem client,其余参数(name、blockNumber、blockTag、gatewayUrls、assetGatewayUrls、universalResolverAddress等)原样透传给 viem。
源码剖析:Hook 的底层工作原理
结合 packages/react/src/hooks/useEnsAvatar.ts 可看到完整实现:
export function useEnsAvatar< config extends Config = ResolvedRegister['config'], selectData = GetEnsAvatarData, >( parameters: UseEnsAvatarParameters<config, selectData> = {}, ): UseEnsAvatarReturnType<selectData> { const config = useConfig(parameters) const chainId = useChainId({ config }) const options = getEnsAvatarQueryOptions(config, { ...parameters, chainId: parameters.chainId ?? chainId, }) return useQuery(options) }其工作流程为:
- 读取配置:通过
useConfig(parameters)获取 wagmi 配置(若传入了config参数则优先使用,否则从最近的WagmiProvider上下文获取); - 确定链 ID:通过
useChainId({ config })获取当前链 ID,若未显式传入chainId,则以当前链为准; - 构造查询选项:调用
getEnsAvatarQueryOptions生成enabled、queryFn、queryKey; - 执行查询:交给 TanStack Query 的
useQuery驱动拉取、缓存与重渲染。
查询键与 enabled 逻辑
查询选项的核心逻辑位于 packages/core/src/query/getEnsAvatar.ts:
return { ...options.query, enabled: Boolean(options.name && (options.query?.enabled ?? true)), queryFn: async (context) => { const [, { scopeKey: _, ...parameters }] = context.queryKey if (!parameters.name) throw new Error('name is required') return getEnsAvatar(config, { ...parameters, name: parameters.name }) }, queryKey: getEnsAvatarQueryKey(options), }关键点:
enabled与name强绑定:只要name未提供,查询就不会执行(即使手动设置了query.enabled = true);- 查询键格式:
['ensAvatar', { chainId, name, ... }],由getEnsAvatarQueryKey生成(见 packages/core/src/query/getEnsAvatar.ts#L47-L53)。查询键中包含chainId与name,意味着不同链或不同名称的头像查询互不共享缓存; scopeKey参与缓存隔离:scopeKey会保留在查询键中(filterQueryOptions不会过滤它),因此传入不同scopeKey的相同查询会各自独立缓存。
测试用例印证
仓库中的测试 packages/react/src/hooks/useEnsAvatar.test.ts 验证了默认行为:
test('default', async () => { const { result } = await renderHook(() => useEnsAvatar({ name: 'wevm.eth' }), ) await vi.waitUntil(() => result.current.isSuccess, { timeout: 10_000 }) expect(result.current).toMatchInlineSnapshot(` { "data": "https://euc.li/wevm.eth", ... "queryKey": [ "ensAvatar", { "chainId": 1, "name": "wevm.eth", }, ], ... } `) })该用例确认了两个事实:
- 对
wevm.eth查询成功后会返回头像 URL(测试快照中的值为https://euc.li/wevm.eth); - 生成的查询键为
["ensAvatar", { chainId: 1, name: "wevm.eth" }],与源码中getEnsAvatarQueryKey的实现一致。
小结
useEnsAvatar是 wagmi 中获取 ENS 头像的推荐方式:它封装了 viem 的底层解析能力,并通过 TanStack Query 提供开箱即用的缓存、重试、依赖查询与 SSR 支持。使用时牢记两点:传入前用normalize规范化 ENS 名称、name缺失时查询会被自动禁用。若需在非 React 环境使用同一能力,可直接调用getEnsAvataraction 或从wagmi/query导入查询工具函数。更多相关能力可参考useEnsAddress等 ENS 系列 Hook 的文档。
【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考