如何将已知接口属性与自定义索引签名相结合



您如何键入一个可以同时具有几个的对象,例如:

{ 
    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个单独的索引签名,一个用于stringnumber

在您的示例中,hellomoo使字符串索引无法使用,但是您可以劫持自定义方法的数字索引

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
    }
    

最新更新