Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.2k views
in Technique[技术] by (71.8m points)

typescript - How to combine known interface properties with a custom index signature?

How do you type an object that can have both a few declared optional properties, e.g.:

{ 
    hello?: string, 
    moo?: boolean 
}

as well as custom properties (that must be functions), e.g.:

    [custom: string]: (v?: any) => boolean

This is what I'd like to see for example:

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

Trying to add an index signature to an interface with known keys (i.e. hello?: string, moo?: boolean) requires all keys to be subsets of the index signature type (in this case, a function returning a boolean). This obviously fails.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

The question accepted by the owner (until now) is incorrect.

Here's how you can do it:

You need to make the index signature a union type of all the types that can be contained in the interface:

interface IExample {
    hello?: string;
    moo?: boolean;
    [custom: string]: string | boolean | YourFunctionType;
}

interface YourFunctionType {
    (v?: any): boolean;
}

Please note that I've extracted your function type into a separate interface to improve readability.

Implications:

This means, that the explicitly defined properties are well supported by 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

However all properties from the index signature now need to be checked using type guards which adds a little bit of a runtime overhead:

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
}

On the other hand: the runtime overhead is necessary because it might be that test is accessed like this:

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
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...