打字稿 3.7 部分,不可分配给类型 never/undefined

pio*_*jow 7 typescript

将 typescript 版本升级到 3.7 后,我在 2 个函数中遇到了类型错误。功能正常工作(添加@ts-ignore 时一切正常)。

interface A {
    x: string,
    y: number,
}

interface B {
    x: string,
    y: number,
    z: boolean,
}

function extract(input: B, keys: Array<keyof A>): Partial<A> {
    const extract: Partial<A> = {};
    keys.forEach((key: keyof A) => {
        extract[key] = input[key]; // error!
    //  ~~~~~~~~~~~~ <-- 'string | number' is not assignable to 'undefined'    
    });
    return extract;
}

function assign(target: B, source: Partial<A>): void {
    (Object.keys(source) as Array<keyof A>).forEach((key) => {
        target[key] = source[key]!; // error!
    //  ~~~~~~~~~~~ <-- 'string | number' is not assignable to type 'never'
    });
}

const test: B = { x: "x", y: 1, z: true };
console.log(extract(test, ["y"])); // -> { y: 1 }
assign(test, { x: "new" });
console.log(test); // -> { x: "new", y: 1, z: true }
Run Code Online (Sandbox Code Playgroud)

代码和错误可以在ts playground找到

有没有办法在没有@ts-ignore 的情况下以正确的方式实现这一点?

jca*_*alz 5

这是TypeScript 3.5 中引入的一个已知的重大更改,以防止对索引访问类型的不良写入。正如您所见,它通过捕获实际错误产生了一些良好的效果,并且通过对完全安全的分配发出错误警告而产生了一些不幸的影响。

解决这个问题的最简单方法是使用类型断言

(extract as any)[key] = input[key];
(target as any)[key] = source[key];
Run Code Online (Sandbox Code Playgroud)

有比 更安全的断言any,但表达起来更复杂。


如果要避免类型断言,则需要使用一些变通方法。对于extract(),在 内部使用通用回调函数就足够了forEach()。编译器将赋值视为来自和指向相同泛型类型的值Partial<A>[K],它允许:

function extract(input: B, keys: Array<keyof A>): Partial<A> {
    const extract: Partial<A> = {};
    keys.forEach(<K extends keyof A>(key: K) => {
        extract[key] = input[key];
    });
    return extract;
}
Run Code Online (Sandbox Code Playgroud)

对于assign()target[key] = source[key]不会与通用甚至工作key类型K。您正在读取泛型类型NonNullable<Partial<A>[K]>并写入不同的泛型类型B[K]。(我的意思是“不同”,因为编译器不会以相同的方式表示它们;当然,当您评估它们时它们是相同的类型。)我们可以通过将target变量扩大到Partial<A>(这很好,因为每个B也是一个Partial<A>,如果你眯着眼睛不考虑突变)。

所以我会这样做:

function assign(target: B, source: Partial<A>): void {
    const keys = Object.keys(source) as Array<keyof A>;
    const widerTarget: Partial<A> = target;
    keys.forEach(<K extends keyof A>(key: K) => {
        if (typeof source[key] !== "undefined") { // need this check
            widerTarget[key] = source[key];
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

哦,我添加了该undefined支票,因为assign(test, { x: "new", y: undefined })是允许的;该语言并没有真正区分缺失与undefined.


无论如何,这些将按需要工作。就我个人而言,我可能只是使用类型断言并继续前进。

好的,希望有帮助;祝你好运!

代码链接