这是我的代码
async getAll(): Promise<GetAllUserData[]> {
return await dbQuery(); // dbQuery returns User[]
}
class User {
id: number;
name: string;
}
class GetAllUserData{
id: number;
}
Run Code Online (Sandbox Code Playgroud)
getAll函数返回User[],并且数组的每个元素都有name属性,即使它的返回类型是GetAllUserData[]。
我想知道是否可以out of the box在打字稿中将对象限制为仅由其类型指定的属性。
Gre*_*egL 39
我想出了一种方法,使用自 TypeScript 版本 3 以来可用的内置类型,以确保传递给函数的对象不包含任何超出指定(对象)类型的属性。
// First, define a type that, when passed a union of keys, creates an object which
// cannot have those properties. I couldn't find a way to use this type directly,
// but it can be used with the below type.
type Impossible<K extends keyof any> = {
[P in K]: never;
};
// The secret sauce! Provide it the type that contains only the properties you want,
// and then a type that extends that type, based on what the caller provided
// using generics.
type NoExtraProperties<T, U extends T = T> = U & Impossible<Exclude<keyof U, keyof T>>;
// Now let's try it out!
// A simple type to work with
interface Animal {
name: string;
noise: string;
}
// This works, but I agree the type is pretty gross. But it might make it easier
// to see how this works.
//
// Whatever is passed to the function has to at least satisfy the Animal contract
// (the <T extends Animal> part), but then we intersect whatever type that is
// with an Impossible type which has only the keys on it that don't exist on Animal.
// The result is that the keys that don't exist on Animal have a type of `never`,
// so if they exist, they get flagged as an error!
function thisWorks<T extends Animal>(animal: T & Impossible<Exclude<keyof T, keyof Animal>>): void {
console.log(`The noise that ${animal.name.toLowerCase()}s make is ${animal.noise}.`);
}
// This is the best I could reduce it to, using the NoExtraProperties<> type above.
// Functions which use this technique will need to all follow this formula.
function thisIsAsGoodAsICanGetIt<T extends Animal>(animal: NoExtraProperties<Animal, T>): void {
console.log(`The noise that ${animal.name.toLowerCase()}s make is ${animal.noise}.`);
}
// It works for variables defined as the type
const okay: NoExtraProperties<Animal> = {
name: 'Dog',
noise: 'bark',
};
const wrong1: NoExtraProperties<Animal> = {
name: 'Cat',
noise: 'meow'
betterThanDogs: false, // look, an error!
};
// What happens if we try to bypass the "Excess Properties Check" done on object literals
// by assigning it to a variable with no explicit type?
const wrong2 = {
name: 'Rat',
noise: 'squeak',
idealScenarios: ['labs', 'storehouses'],
invalid: true,
};
thisWorks(okay);
thisWorks(wrong1); // doesn't flag it as an error here, but does flag it above
thisWorks(wrong2); // yay, an error!
thisIsAsGoodAsICanGetIt(okay);
thisIsAsGoodAsICanGetIt(wrong1); // no error, but error above, so okay
thisIsAsGoodAsICanGetIt(wrong2); // yay, an error!
Run Code Online (Sandbox Code Playgroud)
Typescript 使用结构类型而不是名义类型来确定类型相等。这意味着类型定义实际上只是该类型对象的“形状”。这也意味着任何共享另一种类型“形状”子集的类型都隐式地是该类型的子类。
在您的示例中,因为 aUser具有 的所有属性GetAllUserData,User所以隐式是 的子类型GetAllUserData。
为了解决这个问题,您可以专门添加一个虚拟属性,使您的两个类彼此不同。这种类型的属性称为鉴别器。(在这里搜索歧视工会)。
您的代码可能如下所示。鉴别器属性的名称并不重要。这样做会产生你想要的类型检查错误。
async function getAll(): Promise<GetAllUserData[]> {
return await dbQuery(); // dbQuery returns User[]
}
class User {
discriminator: 'User';
id: number;
name: string;
}
class GetAllUserData {
discriminator: 'GetAllUserData';
id: number;
}
Run Code Online (Sandbox Code Playgroud)
我认为您拥有的代码结构是不可能的。Typescript确实有多余的属性检查,这听起来像您所追求的,但它们仅适用于对象文字。从这些文档:
对象文字在将它们分配给其他变量或将它们作为参数传递时会得到特殊处理并进行额外的属性检查。
但是返回的变量不会经过该检查。所以虽然
function returnUserData(): GetAllUserData {
return {id: 1, name: "John Doe"};
}
Run Code Online (Sandbox Code Playgroud)
会产生错误“Object literal may only specified known properties”,代码:
function returnUserData(): GetAllUserData {
const user = {id: 1, name: "John Doe"};
return user;
}
Run Code Online (Sandbox Code Playgroud)
不会产生任何错误,因为它返回一个变量而不是对象文字本身。
因此,对于您的情况,由于getAll不返回文字,因此打字稿不会进行多余的属性检查。
最后说明:“精确类型”存在一个问题,如果实施该问题将允许您在此处进行所需的检查。
继GregL的回答之后,我想添加对数组的支持,并确保如果你有一个数组,数组中的所有对象都没有额外的道具:
type Impossible<K extends keyof any> = {
[P in K]: never;
};
export type NoExtraProperties<T, U extends T = T> = U extends Array<infer V>
? NoExtraProperties<V>[]
: U & Impossible<Exclude<keyof U, keyof T>>;
Run Code Online (Sandbox Code Playgroud)
注意:只有当您拥有 TS 3.7(包含)或更高版本时,类型递归才可能。
不幸的是,这在 Typescript 中目前是不可能的,并且与 TS 类型检查的形状性质有些矛盾。
此线程中基于泛型的答案NoExtraProperties非常优雅,但不幸的是它们不可靠,并且可能导致难以检测到错误。
我将用 GregL 的回答来证明。
// From GregL's answer
type Impossible<K extends keyof any> = {
[P in K]: never;
};
type NoExtraProperties<T, U extends T = T> = U & Impossible<Exclude<keyof U, keyof T>>;
interface Animal {
name: string;
noise: string;
}
function thisWorks<T extends Animal>(animal: T & Impossible<Exclude<keyof T, keyof Animal>>): void {
console.log(`The noise that ${animal.name.toLowerCase()}s make is ${animal.noise}.`);
}
function thisIsAsGoodAsICanGetIt<T extends Animal>(animal: NoExtraProperties<Animal, T>): void {
console.log(`The noise that ${animal.name.toLowerCase()}s make is ${animal.noise}.`);
}
const wrong2 = {
name: 'Rat',
noise: 'squeak',
idealScenarios: ['labs', 'storehouses'],
invalid: true,
};
thisWorks(wrong2); // yay, an error!
thisIsAsGoodAsICanGetIt(wrong2); // yay, an error!
Run Code Online (Sandbox Code Playgroud)
如果在将对象传递给thisWorks/ thisIsAsGoodAsICanGetTS 时识别出该对象具有额外的属性,则此方法有效。但在 TS 中,如果它不是一个对象字面量,一个值总是可以有额外的属性:
const fun = (animal:Animal) =>{
thisWorks(animal) // No Error
thisIsAsGoodAsICanGetIt(animal) // No Error
}
fun(wrong2) // No Error
Run Code Online (Sandbox Code Playgroud)
所以,在thisWorks/thisIsAsGoodAsICanGetIt你不能相信动物参数没有额外的属性。
只需使用pick(Lodash、Ramda、Underscore)。
interface Narrow {
a: "alpha"
}
interface Wide extends Narrow{
b: "beta"
}
const fun = (obj: Narrow) => {
const narrowKeys = ["a"]
const narrow = pick(obj, narrowKeys)
// Even if obj has extra properties, we know for sure that narrow doesn't
...
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
20415 次 |
| 最近记录: |