下面是一个名为MY_OBJECT的对象的示例:

export type ObjectDetails = {
    a: string,
    b?: string
};

export type MyObjectType = { [key: string]: ObjectDetails };

export const MY_OBJECT: MyObjectType = {
    BANANA: {
        a: "a"
    },
    LEMON: {
        a: "A"
    }
}

export type MyObjectKey = keyof typeof MY_OBJECT;

现在,一个方法应该返回一个使用MY_OBJECT中的键作为键的对象(但不必全部使用它们),值可以是stringnumber.所以,我创建了这个类型:

type MyType = { [key: MyObjectKey]: string | number };

如果我创建这个对象并使用一个不在MY_OBJECT中的键,我必须如何修改我的代码以使其抛出编译错误?

const newObject: MyType = {
    BANANA: "banana",
    APPLE: "apple" // <-- this should not compile
};

我想我正确地理解了它目前没有抛出错误,因为我已经将MY_OBJECT键类型设置为MyObjectType中的string.我真的必须首先为MY_OBJECT中所有可能的关键点创建一个类型,然后在MyObjectType中使用它吗?我不想那样做.

推荐答案

这里的主要问题是,当您为一个像MY_OBJECT这样的变量加上一个像MyObjectType这样的(非union)类型时,类型判断器所知道的关于它的类型就这么多了.它不会记住有关变量初始值设定项的任何更具体的信息;这样的知识将永远丢失.所以typeof MY_OBJECT只是MyObjectType,keyof typeof MY_OBJECT只是string.

而不是注释,您真的只希望将MY_OBJECT‘S初始化器是有效的MyObjectType,而不是将其完全扩大到MyObjectType.这看起来像是一份the satisfies operator人的工作.如果你写的是const vbl = expr satisfies Type而不是const vbl: Type = expr,你通常会得到更令人满意的行为:

const MY_OBJECT = {
    BANANA: {
        a: "a"
    },
    LEMON: {
        a: "A"
    }
} satisfies MyObjectType // <-- do this instead

如果初始值设定项不满足MyObjectType,则仍会发出警告:

const MY_OBJECT = {
    BANANA: {
        a: "a"
    },
    LEMON: {
        a: 1 // error!
      //~ <-- Type 'number' is not assignable to type 'string'.
    }
} satisfies MyObjectType;

现在MY_OBJECT的类型比MyObjectType更具体,你可以通过智能感知观察到:

/* const MY_OBJECT: {
    BANANA: { a: string; };
    LEMON: { a: string; };
} */

你可以根据需要获得它的密钥:

type MyObjectKey = keyof typeof MY_OBJECT;
// type MyObjectKey = "BANANA" | "LEMON"

现在,您可以相应地定义MyType.您不能为此使用index signature;索引签名只支持像string这样的"宽"类型.您有一组已知的密钥.你应该用mapped type代替.由于您希望这些属性是可选的,因此可以使用? mapping modifier:

type MyType = { [K in MyObjectKey]?: string | number }; 
/* type MyType = {
    BANANA?: string | number | undefined;
    LEMON?: string | number | undefined;
} */

现在你会得到你想要的行为:

const newObject: MyType = {
    BANANA: "banana",
    APPLE: "apple" // <-- error
};

不过,最后要提的是.仅当您在需要没有该属性的类型的位置直接使用object literal时,才会发生类似上面APPLE的错误.它们主要是Linter警告,您可能会因为使用了编译器会立即忘记的键而犯了错误.它们并不是一个类型错误,您可以通过分多个步骤进行赋值来避免检测,如下所示:

const obj = { BANANA: "banana", APPLE: "apple" };
const newObject2: MyType = obj; // this will never be an error.

根据编译器的说法,这很好,因为obj知道大约APPLE.这有点超出了本文的范围,因为一切都如您所愿地工作.但无论如何,你都应该意识到这一点.有关更多信息,请参见Typescript: prevent assignment of object with more properties than is specified in target interfaceWhy are excess properties allowed in Typescript when all properties in an interface are optional?typescript: validate excess keys on value, returned from functionUnexpected behaviour of Typescript Record<number, Type> type,或任何其他提及"超额财产"和"Typescript "的问题.

Playground link to code

Typescript相关问答推荐

Typescript对每个属性具有约束的通用类型

类型断言添加到类型而不是替换

VS代码1.88.0中未出现自动导入建议

对数组或REST参数中的多个函数的S参数进行类型判断

如何将泛型对象作为返回类型添加到类型脚本中

为什么不能使用$EVENT接收字符串数组?

如何在ANGLE中注册自定义验证器

TypeScrip泛型类未提供预期的类型错误

如何通过TypeScrip中的函数防止映射类型中未能通过复杂推理的any值

为什么我的导航在使用Typescript的React Native中不起作用?

RXJS将对键盘/鼠标点击组合做出react

TaskListProps[]类型不可分配给TaskListProps类型

从联合提取可调用密钥时,未知类型没有调用签名

React-跨组件共享功能的最佳实践

是否使用类型将方法动态添加到类?

无法使用prisma中的事务更新记录

TypeScript是否不知道其他函数内部的类型判断?

Typescript 是否可以区分泛型参数 void?

具有枚举泛型类型的 Typescript 对象数组

如何在Typescript 中定义具有相同类型的两个泛型类型的两个字段?