How to write a function type that strictly requires its parameters in Typescript

I ran into a problem when combineReducersI wasn’t strict enough and I’m not sure how to get around it:

interface Action {
  type: any;
}

type Reducer<S> = (state: S, action: Action) => S;

const reducer: Reducer<string> = (state: string, action: Action) => state;

const reducerCreator = (n: number): Reducer<string> => reducer;

interface ReducersMapObject {
  [key: string]: Reducer<any>;
}

const reducerMap: ReducersMapObject = {
  test: reducer,
  test2: reducerCreator
}

I would expect it to throw an reducerMaperror because the Creative reducer is not a reducer (it is a function that takes a string and returns a reducer), but TypeScript is okay with that.

It seems that the source of the problem is that Reducer essentially boils down to any => any, because functions with fewer parameters are assigned to functions that take more parameters .

This means that the type ReducersMapObjectis basically equal{[key: string]: function}

Reducer , , ReducersMapObject ?

TypeScript ,

+4
1

... . , .

1:

, ...

- TypeScript.

1:

, , " " . , " " "" . . FAQ. , , " " .

const add: (x: number, y: number) = 
           (x: number) => { return x; }; // works and is safe

2:

, .  , . Pair , Pair.

class Pair {
    x: number;
    y: number;
}

let addPair: (p: Pair) => number;

Pair.

TypeScript , . TypeScript Pair Single, Single Pair. .

class Single {
    x: number;
}

let s: Single = new Pair(); // works
let p: Pair = new Single(); // fails because of a missing property.

TypeScripts, , .

let addSingle: (s: Single) => number; 
addSingle = (p: Pair) => p.x + p.y; // as expected, Pair is assignable to Single.

let addPair: (p: Pair) => number;
addPair = (s: Single) => s.x; // surprise, Single is assignable to Pair!

, , Pair Single.

Reducers

( ), Reducer.

class Action { }

// no restriction - TypeScript allows discarding function parameters 
type Reducer01<S> = (state: S, action: Action) => S;
const reducer01: Reducer01<number> = (state: number) => 0; // works

// no restriction - TypeScript functions have parameter bivariance
class ReducerArgs<S> { 
    state: S;
    action: Action;
}
type Reducer02<S> = (args: ReducerArgs<S>) => S;
const reducer02 = (args: { state: number }) => 0; // works

, , , ReducersMapObject Reducer . - , :

  • Reducer Reducer
  • Reducer ( ) .

2:

... , ReducersMapObject ?

, , - reducerCreator ( ) Reducer<S>. .

1:

, , ReducerArgs<S>, . , , , reducerCreator. :

interface Action {
  type: any;
}

// define an interface as the parameter for a Reducer<S> function
interface ReducerArgs<S> { 
    state: S;
    action: Action
}

type Reducer<S> = (args: ReducerArgs<S>) => S;

const reducer: Reducer<string> = (args: ReducerArgs<string>) => args.state;

const reducerCreator = (n: number): Reducer<string> => reducer;

interface ReducersMapObject {
  [key: string]: Reducer<any>;
}

const reducerMap: ReducersMapObject = {
  test: reducer,
  test2: reducerCreator // error!
}

2:

- ReducerMapObject<T> :

interface ReducersMapObject<T> {
  [key: string]: Reducer<T>;
}

, .

const reducer: Reducer<string> = (state: string, action: Action) => state;
const reducer1: Reducer<number> = (state: number, action: Action) => state;

const reducerMap: ReducersMapObject<string | number> = {
    test: reducer,
    test1: reducer1,
    test2: reducerCreator // error!
}

, any => any T => T, T , . ( , , : "x , , y".)

, . . !

+4

Source: https://habr.com/ru/post/1684540/


All Articles