✨ 背景介绍
在最近写 Vue 3 项目的时候,我用了 vue-i18n
来做国际化。
在组件里用 useI18n().t()
或 i18n.global.t()
都挺顺手的,直到有一天,我在一个普通的 .ts
工具函数中也想调用 t('xxx')
来取文案,结果就踩了坑。
于是我想着封装一下 t()
方法,抽出来统一用。
作为一名不愿意写重复代码的 TypeScript 信徒,我第一时间写下了这样的代码:
初步尝试:用 Parameters 封装
最初的写法:
ts
import { createI18n } from 'vue-i18n'
const messages = {
en: { hello: 'Hello' },
zh: { hello: '你好' },
}
const i18n = createI18n({
legacy: false,
locale: 'zh',
messages,
})
type I18nParams = Parameters<typeof i18n.global.t>;
const t = (...args: I18nParams): string => i18n.global.t(...args);
// 期望输出:你好
t('hello')
结果:t 报错!
"应有 3 个参数,但获得 1 个"
问题分析
🧠 为什么报错 "应有 3 个参数,但获得 1 个"
vue-i18n
的 .t
方法有多个重载形式,可能是:
ts
t(key: string): string
t(key: string, locale: string): string
t(key: string, values: Record<string, unknown>): string
t(key: string, values: unknown[], locale: string): string
// 等等...
所以 Parameters<typeof i18n.global.t>
推断出来的是个联合类型:
ts
type I18nParams = [string] | [string, string] | [string, Record<string, unknown>] | ...
但是我这样写:
ts
const t = (...args: I18nParams): string => i18n.global.t(...args);
会报错,因为 ...args
要求是一个具体的元组类型 ,但 I18nParams
是联合类型,TS 不知道你是哪一个,不能展开。
一通操作猛如虎:我选择了 bind
最终解决方案:
ts
export const t = i18n.global.t.bind(i18n.global);
// 绑定 this,保留函数签名,类型不丢失
优点:
- ✅ 类型安全(保留原函数的签名)
- ✅ 没有 this 绑定问题
- ✅ 不用手动写 overload
- ✅ 简洁
推荐使用方式
ts
import { t } from '@/utils/i18n';
t('common.ok');
t('user.greet', { name: '斌哥' });
总结 & 建议
- 遇到重载的 API,谨慎使用
Parameters<>
,尤其是推断出来的是联合类型 bind
是处理带this
上下文 API 的最佳方式- 在 Vue 组件之外使用 i18n,建议封装一下
t
,统一管理
插播一下:Parameters 本身没错,只是用错地方了
虽然这次我在封装 i18n.global.t
的时候,使用 Parameters<>
遇到了联合类型导致的类型报错,但其实 Parameters
在绝大多数场景下,依然是非常有用的工具类型。
举几个它非常好用的例子:
单一签名函数的参数复用
ts
function greet(name: string, age: number): void {}
type GreetParams = Parameters<typeof greet>;
// 推断为 [name: string, age: number]
const callGreet = (...args: GreetParams) => greet(...args);
在这种没有重载的函数中,Parameters
非常稳健,能复用参数类型,减少重复定义。
简单封装函数时的"偷懒利器"
ts
function log(message: string): void {}
type LogArgs = Parameters<typeof log>; // [message: string]
const logWithTimestamp = (...args: LogArgs) => {
console.log(new Date(), ...args);
}
🚀 如果你在使用 vue-i18n + TypeScript 时也遇到类似的问题,希望这篇文章能帮你少踩一个坑。
如果你有更好的封装方式或不同看法,欢迎评论区一起探讨!