[Typescript] ts-toolbelt F.Narrow preserve the exactly data for function arguement

发布时间 2023-05-03 01:58:00作者: Zhentiw

Example code:

interface Fruit {
  name: string;
  price: number;
}

export const wrapFruit = <TFruits extends Fruit[]>(fruits: TFruits) => {
  const getFruit = (name: unknown): unknown => {
    return fruits.find((fruit) => fruit.name === name);
  };

  return {
    getFruit,
  };
};

const fruits = wrapFruit([
  {
    name: 'apple',
    price: 1,
  },
  {
    name: 'banana',
    price: 2,
  },
]);

 

The wrapFruit function has following types:

We saw that <{name: string, price: nunmber}> which is not what we want.

 

We want:

 

To do that we need to use the F.Narrowtype helper from 'ts-toolbelt'

export const wrapFruit = <TFruits extends Fruit[]>(fruits: F.Narrow<TFruits>) => {
  const getFruit = (name: unknown): unknown => {
    return fruits.find((fruit) => fruit.name === name);
  };

  return {
    getFruit,
  };
};


 

Narrow:

export declare type Try<A1 extends any, A2 extends any, Catch = never> = A1 extends A2 ? A1 : Catch;
export declare type Narrowable = string | number | bigint | boolean;
export declare type NarrowRaw<A> = (A extends [] ? [] : never) | (A extends Narrowable ? A : never) | ({
    [K in keyof A]: A[K] extends Function ? A[K] : NarrowRaw<A[K]>;
});
declare type Narrow<A extends any> = Try<A, [], NarrowRaw<A>>;

function fn<T>(inputs: T) {}

fn([{name: 'apple', price: 1}])

 

With Typescript V5.0, there will be a <const T> https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-beta/#const-type-parameters

Which doing pretty much the same thing as F.Narrow.

 

Another appraoch

interface Fruit {
  name: string;
  price: number;
}

export const wrapFruit = <const T extends Fruit>(fruits: T[]) => {
  const getFruit = <TName extends T['name']>(name: TName) => {
    return fruits.find((fruit) => fruit.name === name) as Extract<T, {name: TName}>;
  };

  return {
    getFruit,
  };
};

const fruits = wrapFruit([
  {
    name: "apple",
    price: 1,
  },
  {
    name: "banana",
    price: 2,
  },
]);

const banana = fruits.getFruit("banana");
const apple = fruits.getFruit("apple");
// @ts-expect-error
const notAllowed = fruits.getFruit("not-allowed");

type tests = [
  Expect<Equal<typeof apple, { readonly name: "apple"; readonly price: 1 }>>,
  Expect<Equal<typeof banana, { readonly name: "banana"; readonly price: 2 }>>
];