简体   繁体   English

为什么 TypeScript 允许 Function 返回不安全值

[英]Why is TypeScript Allowing Function to Return Unsafe Value

Why does TypeScript accept the definition of seta when it does not return objects of type A ?为什么 TypeScript 在不返回A类型的对象时接受seta的定义?

type A = {
    a: '123',
    b: '456'
}

// Returns copy of obj with obj[k] = '933'
function seta<K extends keyof A>(k: K, obj: A): A {
    return {
        ...obj,
        [k]: '933'
    }
}

const w: A = seta('a', {
    a: '123',
    b: '456'
})

// now w = {a: '933', b: '456'}

https://tsplay.dev/wEGX4m https://tsplay.dev/wEGX4m

It's accepted because if you hover on [k]: '933', it says它被接受是因为如果你在[k]: '933',它会说

(parameter) k: K extends keyof A (参数)k:K扩展keyof A

Which means you're returning a property that's extended from the return type, which is allowed.这意味着您要返回一个从返回类型扩展而来的属性,这是允许的。

When you declare that an object must be of an interface, you are saying that it must have at least those properties, not only those properties.当您声明 object 必须是接口时,您是说它必须至少具有那些属性,而不仅仅是那些属性。

This looks like a bug or limitation in TypeScript;这看起来像是 TypeScript 中的错误或限制; see microsoft/TypeScript#37103 (labeled a bug) or microsoft/TypeScript#32236 (labeled "needs investigation").请参阅microsoft/TypeScript#37103 (标记为错误)或microsoft/TypeScript#32236 (标记为“需要调查”)。

When you spread properties into an object literal and then add a computed property , it seems that TypeScript will completely ignore the computed property unless the computed key is of a single, specific, literal type (not a union of literals, and not a generic type parameter constrained to a string literal):当您将属性传播到 object 文字中然后添加计算属性时,似乎 TypeScript 将完全忽略计算属性,除非计算键是单个特定的文字类型(不是文字的联合,也不是泛型类型参数限制为字符串文字):

function testing<K extends "a" | "b">(a: "a", x: string, y: "a" | "b", z: K) {
    const someObject = { a: "v" } // { a: string }
    const objA = { ...someObject, [a]: 0 } // { a: number } 👍
    const objX = { ...someObject, [x]: 0 } // { a: string } 👎
    const objY = { ...someObject, [y]: 0 } // { a: string } 👎
    const objZ = { ...someObject, [z]: 0 } // { a: string } 👎
}

Computed property keys in general are a bit problematic in TypeScript, even without spreading, as they tend to get widened all the way to string , losing information you might care about (see another bug at microsoft/TypeScript#13948 ):通常,计算的属性键在 TypeScript 中有点问题,即使没有传播,因为它们往往会一直加宽到string ,丢失您可能关心的信息(请参阅microsoft/TypeScript#13948中的另一个错误):

function testing2<K extends "a" | "b">(a: "a", x: string, y: "a" | "b", z: K) {
    const objA = { [a]: 0 } // { a: number } 👍
    const objX = { [x]: 0 } // { [x: string]: number; } 🤷‍♂️
    const objY = { [y]: 0 } // { [x: string]: number; } 🤷‍♂️
    const objZ = { [z]: 0 } // { [x: string]: number; } 🤷‍♂️
}

So that's the problem you're having here.所以这就是你在这里遇到的问题。


I don't know what to say other than "tread lightly around computed properties".除了“轻视计算属性”之外,我不知道该说什么。 You can kind of work around it by defining your own function that produces a more "correct" version of what a computed property would be:你可以通过定义你自己的 function 来解决它,它会产生一个更“正确”的计算属性版本:

function kv<K extends PropertyKey, V>(k: K, v: V) {
    return { [k]: v } as { [P in K]: { [Q in P]: V } }[K]
}

const k = Math.random() < 0.5 ? "a" : "b";

const obj = kv(k, Math.random());
/* const obj: {
    a: number;
} | {
    b: number;
} */

You can see that a union of keys produces a union of objects.您可以看到键的联合产生对象的联合。 But if the key is generic then the compiler leaves that unevaluated and spread with generics tends to be represented as an intersection , which is often acceptable but not when keys overlap.但是,如果键是通用的,那么编译器会留下未评估和传播的 generics 倾向于表示为交集,这通常是可以接受的,但当键重叠时就不行了。 (See microsoft/TypeScript#32022 for that issue): (有关该问题,请参阅microsoft/TypeScript#32022 ):

function setA2<K extends keyof A>(k: K, obj: A): A {
    return {
        ...obj,
        ...kv(k, '933')
    } 
    /* const ret1: A & { [P in K]: { [Q in P]: string; }; }[K] */ // no error
}

So you'd have to widen k from K to keyof A before you got any warning:因此,在收到任何警告之前,您必须将kK扩大到keyof A

function setA3<K extends keyof A>(k: K, obj: A): A {
    const kW: keyof A = k;
    return {
        ...obj,
        ...kv(kW, '933')
    }; // FINALLY AN ERROR
    // Type '{ a: string; b: "456"; } | { b: string; a: "123"; }' is not assignable to type 'A'.
}

So, hooray, I guess... you can spend a lot of effort to wrestle some type safety out of this function, but it's quite a pyrrhic victory (I think that's a word).所以,万岁,我想......你可以花很多精力从这个 function 中争取一些类型安全,但这是一个相当昂贵的胜利(我认为这是一个词)。 So again, be careful around computed properties until and unless the relevant GitHub issues are fixed.因此,再次注意计算属性,直到相关的 GitHub 问题得到修复。

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Typescript 不安全地返回“任何”类型的值 - Typescript Unsafe return of an `any` typed value Typescript:ESLint:任何类型值的不安全返回 @typescript-eslint/no-unsafe-return - Typescript : ESLint : Unsafe return of an any typed value @typescript-eslint/no-unsafe-return Typescript 导入 class:不安全返回“任何”类型的值 - Typescript imported class: Unsafe return of an `any` typed value 对 Typescript function 中的 return 语句不安全地使用“any”类型的表达式 - Unsafe use of expression of type 'any' for return statements in Typescript function 异步 function 不允许 typescript 中的两个 promise 类型的返回类型 - Async function not allowing return type of two promise types in typescript TypeScript:为什么输入回调function时这个返回值未知? - TypeScript: Why is this return value unknown when the callback function is typed? 打字稿返回值或咖喱函数 - Typescript return value or curried function TypeScript typeof函数返回值 - TypeScript typeof Function return value 箭头函数中的 Typescript unsafe-return-type linting 错误,但不在正常的函数体中 - Typescript unsafe-return-type linting error in arrow function, but not not in a normal function body UIComponent.getRouterFor 显示 TypeScript 错误:“任何”类型值的不安全返回 - UIComponent.getRouterFor shows TypeScript error: Unsafe return of an 'any' typed value
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM