简体   繁体   中英

Is it possible to define a type representing a function with one parameter without 'any' in TypeScript?

Obviously, this is fairly trivial using the any keyword:

type SingleParamFunction = (arg: any) => any;

const foo: SingleParamFunction = (x: number): number => x + 1;

I've tried the following:

type SingleParamFunction = (arg: unknown) => unknown;

const foo: SingleParamFunction = (x: number): number => x + 1;

However, the compiler complains that 'unknown' is not assignable to type 'number'.

I'm curious as to whether it is possible to do so without resorting to any , perhaps a solution using generics.


A potential use case might look something like the following:

interface Foo<I, O> {
    input: I;
    transform: (x: I) => O;
}
const foos: Foo<any, any>[] = [
    {
        input: 1,
        transform: (x: number) => x + 1,
    },
    {
        input: 'hello',
        transform: (s: string) => s + ' world',
    },
];
for (const foo of foos) {
    console.log(foo.transform(foo.input));
}

There are several solutions to your problem that are equally valid but progressively enhance the type safety.

The first one is the trivial case you presented: (x: any) => any . Despite the "never use any" rule of thumb, a judgemental use of any can be a valid choice. If all you need is to tell the compiler that the function accepts a single argument of any type , then why not?

The second one is more sound and was already proposed by fjc in the comment :

Why not use a generic like this: type SingleParamFunction = (arg: T) => T; const foo: SingleParamFunction = (x) => x + 1;?

This solves the contravariance issue, but the compiler will complain about the signatures of the transform methods because the union will be passed through, so the x and the return type will be inferred as a union (for example, string | number ):

interface Foo<I> {
    input: I;
    transform: SingleParamFunction<I>;
}
const foos: Foo<string | number>[] = [
    {
        input: 1,
        transform: (x) => x + 1, //Type 'string' is not assignable to type 'number'.(2322)
    },
    {
        input: 'hello',
        transform: (s: string) => s + ' world', //Type 'string | number' is not assignable to type 'string'.
    },
];
for (const foo of foos) {
    console.log(foo.transform(foo.input));
}

This is, however, not the solution's problem. Because foos is typed as a mutable array, the compiler does not know what the type of input or transform 's x and return type in a given member is, only that it should be string | number string | number .

If you remove the type annotation, you will see, however, that the type of the array is correctly inferred. This should give you a hint of what you need to check for and leads us to a third solution:

({
    input: number;
    transform: (x: number) => number;
} | {
    input: string;
    transform: (s: string) => string;
})[]

So, how would you do it? Mapped types to the rescue! You need to define an "array of Foo's where for each type in possible input types there possibly is a member that is a Foo of this type". Here is how such a type could look like (with a help of key remapping ):

type validator<I> = Array<{ [T in I as number]: Foo<T> }[number]>;

And voila:

//all ok, valid assignment
const bars: validator<string | number> = [
    {
        input: 1,
        transform: (x: number) => x + 1
    },
    {
        input: 'hello',
        transform: (s: string) => s + ' world'
    },
];

const invalid : validator<string|number> = [
    {
        input: "nope", //Type 'string' is not assignable to type 'number'.(2322)
        transform: (x: number) => x + 1
    },
    {
        input: 'hello',
        transform: (s: string) => Infinity //Type 'number' is not assignable to type 'string'.(2322)
    }, 
];

Playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM