请允许我用一个例子快速回答 "如何用TypeScript定义函数重载类型 "的 "正常 "使用情况。
我想要一个接受回调的函数,如果没有提供回调,则返回一个承诺:
const logResult = result => console.log(`result: ${result}`)
asyncAdd(1, 2).then(logResult) // logs "result: 3"
asyncAdd(3, 6, logResult) // logs "result: 9"
下面是你如何使用普通的JavaScript来实现这个API:
function asyncAdd(a, b, cb) {
const result = a + b
if (cb) return cb(result)
else return Promise.resolve(result)
}
在这个实现中,我们想要的是让TypeScript抓住这个错误的用法:
// @ts-expect-error because when the cb is provided, void is returned so you can't use ".then"!
asyncAdd(1, 2, logResult).then(logResult) // this would throw an error when trying to use ".then" (except we're using TypeScript so it won't even compile 😉)
所以,这里是你如何输入这种重载的方式:
type asyncAddCb = (result: number) => void
// define all valid function signatures
function asyncAdd(a: number, b: number): Promise
function asyncAdd(a: number, b: number, cb: asyncAddCb): void
// define the actual implementation
// notice cb is optional
// also notice that the return type is inferred, but it could be specified as `void | Promise`
function asyncAdd(a: number, b: number, cb?: asyncAddCb) {
const result = a + b
if (cb) return cb(result)
else return Promise.resolve(result)
}
然后你就可以去比赛了!
这篇博文的真正灵感是有点复杂的,需要一些背景信息。
我有一个包叫 babel-plugin-codegen的包,可以让你在编译时生成代码。例如,假设你有一个文件,有以下代码:
// @codegen
const fs = require('fs')
const fruits = fs.readFileSync('./fruit.txt', 'utf8').toString().split('\n')
module.exports = fruits
.map(fruit => `export const ${fruit} = '${fruit}';`)
.join('')
假设fruit.txt 包含一个水果的列表,这就是它的编译结果:
export const apple = 'apple'
export const orange = 'orange'
export const pear = 'pear'
所以,你生成了一串代码,而codegen将其转化为实际的代码,输入到你的输出中。这可以解锁很多非常酷的东西。
但这个babel插件的具体内容并不重要。重要的是,你也可以将其与 babel-plugin-macros这基本上是可导入的babel变换。因此,你可以不配置babel-plugin-codegen,而直接配置babel-plugin-macros ,然后安装 codegen.macro你就以像这样导入和使用它了。
import codegen from 'codegen.macro'
// using as a tagged template literal:
codegen`
module.exports = "const tag = 'this is an example'"
`
// using as a function
codegen(`
module.exports = "const fn = 'this is another example'"
`)
// codegen-ing an external module (and pass an argument):
const jpgs = codegen.require('./get-files-list', '**/*.jpg')
const ui = {`module.exports = require('./some-jsx-code')`}
然后,它可以编译成这样的东西:
// using as a tagged template literal:
const tag = 'this is an example'
// using as a function
const fn = 'this is another example'
// codegen-ing an external module (and pass an argument):
const jpgs = ['kody.jpg', 'olivia.jpg', 'marty.jpg']
const ui = This is some example JSX code
总之,codegen 是很不错的。但是你会注意到这里有一些核心的重载,所以我想我应该分享一下我是如何用TypeScript输入这个函数重载的。
需要记住的是,实际的codegen 函数实现实际上是一个babel宏,所以它看起来与这些函数的工作方式完全不同。它是在编译过程中被调用的,它被调用的参数是AST。
也就是说,到最后,消费者的体验才是最重要的,所以我们需要一个版本的codegen 函数,以预期的方式工作。所以我们要定义我们的类型,然后确保我们把宏函数像普通函数一样投递:
import {createMacro} from 'babel-plugin-macros'
import type {MacroHandler} from 'babel-plugin-macros'
const codegenMacro: MacroHandler = function codegenMacro(/* some args */) {
// the implementation here is irrelevant
}
// use the `createMacro` utility to turn the codegenMacro into a babel macro
const macro = createMacro(codegenMacro)
好的,所以请记住,macro 函数实际上不曾被用户代码调用。这个函数将被babel-plugin-macros ,它将被调用的参数是MacroHandler 。然而,就TypeScript而言,开发者会调用它,所以我们需要给它正确的类型定义,大家都会很高兴。所以让我们来定义这些。
// This handles the tagged template literal API:
declare function codegen(
literals: TemplateStringsArray,
...interpolations: Array
): any
// this handles the function call API:
declare function codegen(code: string): any
// this handles the `codegen.require` API:
declare namespace codegen {
function require(modulePath: string, ...args: Array): any
}
// Unfortunately I couldn't figure out how to add TS support for the JSX form
// Something about the overload not being supported because codegen can't be all the things or whatever
// PRs welcome!
有了这些重载的定义,现在我们只需要强迫TypeScript把我们的macro 文件当作我们定义的codegen 函数。我们还需要使其成为我们的宏文件的默认输出,所以我们将一次完成所有这些。
export default macro as typeof codegen
你可以在babel-plugin-codegensrc/macro.ts 文件中一起浏览它。
我希望这很有用!祝你们好运!