我想要实现一个类型安全的API,它接受一个泛型,并且只有在满足条件时才执行:
const doSomethingWith = (data: unknown) => data
const fn = <T extends unknown>(data: T, opts: { if: boolean }): void => {
if (opts.if == false) return;
doSomethingWith(data)
}
在这种情况下,如何使opts.if
成为有效的谓词?考虑以下情况:
type Data = { abc: string }
// should be valid - checks to make sure data.abc is non-null
const data: Partial<Data> = {}
fn<Data>(data, { if: data.abc != null })
// should be valid - all data is given
const data2: Data = { abc: '123' }
fn<Data>(data2)
// should be invalid - no null check occurs
const data3: Partial<Data> = {}
fn<Data>(data3)
打字游戏场。
我会通过将opts.if
参数作为一个函数而不是静态boolean
值来实现这一点。
通过这种方式,使谓词和作为第一个参数传递的数据类型匹配非常简单。
const doSomethingWith = <T>(data: T) => data
const fn = <T>(data: T, opts?: { if: (t: T) => boolean }): void => {
if (opts?.if(data) === false) return;
doSomethingWith(data)
}
type Data = { abc: string }
const data: Partial<Data> = {}
const data2: Data = { abc: '123' }
const data3: Partial<Data> = {}
fn(data, { if: d => d.abc != null }) // OK, valid predicate
fn(data2) // OK, no predicate
fn(data3, { if: d => d.foobar != null }) // NOT OK, predicte doesn't match data
游乐场
我的第一次尝试太复杂了。但是,当再次阅读你的问题时,我认为我们可以把它变得非常简单。
解决方案
您可以使用函数重载来要求参数有效。
function fn<T>(data: Partial<T>, opts: { if: boolean }): void
function fn<T>(data: T): void
function fn<T>(data: T, opts?: { if: boolean }): void {
if (opts?.if == false) return;
doSomethingWith(data);
}
第三行fn
是实现签名,前两行是必须匹配的函数签名。
说明
您的三个示例都期望doSomethingWith
将使用一个完整的Data
对象进行调用。
如果data
变量不完整,则必须提供opts
。第一个过载签名允许Partial<Data>
,但需要opts
:
function fn<T>(data: Partial<T>, opts: { if: boolean }): void
与示例#1:相匹配
// should be valid - checks to make sure data.abc is non-null
const data: Partial<Data> = {}
fn<Data>(data, { if: data.abc != null })
如果我们已经知道data
变量是完整的,那么您不需要提供任何opts
。这就是这个签名:
function fn<T>(data: T): void
与示例#2匹配:
// should be valid - all data is given
const data2: Data = { abc: '123' }
fn<Data>(data2)
第三个示例失败了,因为它与重载的函数签名都不匹配。由于我们还没有提供opts
参数,所以第一个参数必须是一个完整的Data
对象。但是data3
是Partial<Data>
,所以我们得到一个错误:
// should be invalid - no null check occurs
const data3: Partial<Data> = {}
fn<Data>(data3)
Argument of type 'Partial<Data>' is not assignable to parameter of type 'Data'.
Types of property 'abc' are incompatible.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.(2345)
TypeScript游乐场链接