您如何键入一个可以同时具有几个的对象,例如:
:{
hello?: string,
moo?: boolean
}
以及自定义属性(必须是函数),例如:
[custom: string]: (v?: any) => boolean
这就是我想看到的:
const myBasic: Example = {moo: false}
// -> ✅ Valid! Using known keys
const myValid: Example = {hello: 'world', customYo: () => true}
// -> ✅ Valid! "customYo" is a function returning a bool. Good job!
const myInvalid: Example = {hello: 'world', customYo: 'yo!'}
// -> ☠️ Invalid! "customYo" must be a function returning a boolean
试图将索引签名添加到已知键(即hello?: string, moo?: boolean
)的接口中,所有键都必须是索引签名类型的子集(在这种情况下,返回boolean
的函数)。这显然失败了。
所有者接受的问题不正确。
这是您可以做到的:
您需要使索引签名成为界面中所有可以包含的所有类型的联合类型:
interface IExample {
hello?: string;
moo?: boolean;
[custom: string]: string | boolean | YourFunctionType;
}
interface YourFunctionType {
(v?: any): boolean;
}
请注意,我将您的功能类型提取到单独的接口中以提高可读性。
含义:
这意味着,明确定义的属性得到了TS:
的良好支持const test: IExample = <IExample>{};
test.hello.slice(2); // using a string method on a string --> OK
const isHello = test.hello === true; // ERROR (as expected): === cannot be applied to types string and boolean
const isMoo2 = test.moo === true; // OK
但是,现在需要使用类型守卫检查索引签名的所有属性,这增加了一点点的运行时开销:
test.callSomething(); // ERROR: type 'string | boolean | YourFunctionType' has no compatible call signatures
if (typeof test.callSomething === 'function') { // alternatively you can use a user defined type guard, like Lodash's _.isFunction() which looks a little bit nicer
test.callSomething(); // OK
}
另一方面:运行时开销是必要的,因为可能是这样访问test
:
const propertyName: string = 'moo';
test[propertyName](); // ERROR: resolves to a boolean at runtime, not a function ...
// ... so to be sure that an arbitrary propertyName can really be called we need to check:
const propertyName2: string = 'arbitraryPropertyName';
const maybeFunction = test[propertyName2];
if (typeof maybeFunction === 'function') {
maybeFunction(); // OK
}
这是不可能的,设计https://basarat.gitbooks.io/typescript/docs/types/index-signatures.html
一旦您具有字符串索引签名,所有显式成员也必须符合该索引签名。这是为了提供安全性,以便任何字符串访问都会产生相同的结果。
解决它的唯一方法是利用每个接口可以具有2个单独的索引签名,一个用于string
和number
在您的示例中,hello
和moo
使字符串索引无法使用,但是您可以劫持自定义方法的数字索引
interface IExample {
hello?: string
moo?: boolean
[custom: number]: (v?: any) => boolean
}
const myBasic: IExample = {moo: false}
// -> ✅ Valid! Using known keys
const myValid: IExample = {hello: 'world', 2: () => true}
// -> ✅ Valid! "customYo" is a function returning a bool. Good job!
const myInvalid: IExample = {hello: 'world', 2: 'yo!'}
// -> ☠️ Invalid! "customYo" must be a function returning a boolean
这起作用,但几乎是可接受的接口,这会导致不直觉的功能,您必须通过数组符号来调用它们
myValid.7() // Cannot invoke an expression whose type lacks a call signature. Type 'Number' has no compatible call signatures.
myValid[2]() // works (but ewwwww what is this!!!)
// could alias to more readable locals later but still ewwwwww!!!
const myCustomFunc = myValid[2]
myCustomFunc() // true
这也有警告,即从数字索引返回的类型必须是从字符串索引返回的类型的子类型。这是因为当用数字索引时,JavaScript将数字转换为字符串,然后将索引索引到对象
在这种情况下,您没有明确的字符串索引器,因此字符串索引类型是默认的any
,数字索引类型可以符合
重要这仅适用于科学,我不建议这样做作为现实生活的方法!
实现 @nicbright的答案提出的一种非常简单的方法是使用'高阶类型',它将使用您想要的任何索引签名扩展您已定义的类型,例如P>
type MyOptionalProperties = {
hello?: string,
moo?: boolean
};
type WithCustomFunctions<T> = {
[custom: number]: ((v?: any) => boolean) | T[keyof T];
} & T;
type MyOptionalPropertiesWithCustomFunctions = WithCustomFunctions<MyOptionalProperties>;
关于这种方法的好处是,可以将您的高阶类型应用于其他结构(使用customfunctions的可重复使用)。
坏事是他解决方案的所有含义仍然适用。
聚会迟到,但是您可以使用仿制药并制作一些有条件的工作
type Example<T extends string> = {
[key in T]: key extends "hello"
? string
: key extends "moo"
? boolean
: (v?: any) => boolean
}
这有一些警告:
这仅在Typescript可以自动推断通用类型的上下文中真正起作用:
// This works function checkExample<T extends string>(arg: Example<T>): Example<T> { return arg; } // We can use it to automatically infer the generic type // Here myExample has the correct type Example<...> const myExample = checkExample({hello: 'world', customYo: () => true}); // This does not work, unless you're willing to type a union of all keys const myExample: Example = {hello: 'world', customYo: () => true};
这使得所有密钥都是可选的。如果您需要制作钥匙,则必须与另一种类型相交:
type Example<T extends string> = { [key in T]: key extends "hello" ? string : key extends "moo" // We still need this here ? boolean : (v?: any) => boolean } & { moo: boolean // We declare it here as well }