我try 编写一个函数fn(),具有以下属性:

  1. 采用单个参数x,它是一个带有可选键"a"和"b"的对象(为了简单起见,每个字段可以是数字)
  2. 它输出一个新的对象,其键与输入对象中提供的键相同,但每个字段都是一个特定的类(a—A,b—B),即期望的行为是:>>
  • 如果我调用fn({a:1,b:1}),我收到{a:A,b:B}
  • 如果我调用fn({b:1}),我收到{b:B},
  • 如果我调用fn({}),我会收到{}.

可以将输入对象中的数值作为输出对象中类的初始化值.此外,新字段"c"、"d"、"e"、..可以稍后添加到该功能中.

我很难在类型级别上设置它,但也在JS代码中设置它.我编写了以下代码,它会引发Typescribe错误:

  • 类型"string"不能分配给类型"keyof OutputObject".ts(2344)
  • 类型'Partial'不能分配给类型'Pick OutputObject,K'.ts(2322)<>
class A {}
class B {} 

interface InputObject {
a: number,
b: number
}

interface OutputObject {
a: A,
b: B
}

// My failed attempt at writing fn()

const fn = <
    TObj extends Partial<InputObject>,
    K extends keyof TObj
>(x: TObj): Pick<OutputObject, K> => {

    let returnValue: Partial<OutputObject> = {}

    if (x.a) {
        returnValue.a = new A()
    }

    if (x.b) {
        returnValue.b = new B()
    }

    return returnValue
}

推荐答案

TypeScript将无法遵循fn()实现内部的逻辑来验证它是否符合PickOutputObjectInputObject类型中提取相同密钥的generic mapped type关系.您实际上需要type assertions来放松类型判断以继续进行.这意味着您必须小心代码的行为,因为编译器不能真正判断它.

这里有一个可能的方法:

const fn = <K extends keyof InputObject = never>(
    x: Pick<InputObject, K> & Partial<InputObject>
): Pick<OutputObject, K> => {    
    let returnValue = {} as
        Pick<OutputObject, K> & Partial<OutputObject>;
    if (x.a) { returnValue.a = new A() }    
    if (x.b) { returnValue.b = new B() }    
    return returnValue;
}

其中重要的部分是:

  • 该功能仅在K中通用,x输入类型中存在的键.这使得编译器可以更容易地推断出K,特别是因为我们不希望出现任何额外的键,而xPick<InputObject, K>subtype.

  • 如果你传入{},编译器就不能正确推断出K,通常会回落到keyof InputObject,这与我们想要的正好相反.我们要the never type个,意味着没有 keys . never分中有default type argument分.

  • 输入类型是intersectedPartial<InputObject>,所以编译器在函数内部确定,如果键ab存在属性,那么它是InputObject值类型.这是必要的,因为TS中的对象类型不是密封的.从技术上讲,值{a: 1, b: "hello"}属于类型Pick<InputObject, "a">,因为你可以写interface Foo extends Pick<InputObject, "a"> {b: string}并使用Foo. 我们希望防止这种可能性.

  • returnValue的类型断言在作出时是谎言,因为{}不太可能是类型Pick<OutputObject, K>.但是这个 idea 是,当函数返回时,它将为真.

  • 断言的类型也与前面提到的交叉点相同,原因是Partial<OutputObject>;这让编译器知道可以用ab索引到returnValue.

好吧,我们来测试一下:

const v = fn({ a: 1 });
// const v: Pick<OutputObject, "a">
const w = fn({});
// const w: Pick<OutputObject, never>
const x = fn({ b: 2 });
// const x: Pick<OutputObject, "b">
const y = fn({ a: 1, b: 2 })
// const y: Pick<OutputObject, "a" | "b">

看起来不错输出类型是我们期望的.

Playground link to code

Javascript相关问答推荐

React Hooks中useState的同步问题

对象和数字减法会抵消浏览器js中的数字

GrapeJS -如何保存和加载自定义页面

如何从一个列表中创建一个2列的表?

在react js中使用react—router—dom中的Link组件,分配的右侧不能被 destruct ''

如何在 cypress 中使用静态嵌套循环

如何使用子字符串在数组中搜索重复项

Web Crypto API解密失败,RSA-OAEP

在Matter.js中添加从点A到另一个约束的约束

如何在箭头函数中引入or子句?

如何在Press上重新启动EXPO-AV视频?

在表单集中保存更改时删除';禁用';

在Java脚本中录制视频后看不到曲目

用另一个带有类名的div包装元素

是否设置以JavaScript为背景的画布元素?

调用特定数组索引时,为什么类型脚本不判断未定义

如何使用Cypress在IFRAME中打字

设置复选框根据选中状态输入选中值

Playwright:ReferenceError:browserContext未定义

如何使用Angular JS双击按钮