请教一个类型推断的小问题,如何省去不需要传的参数返回正确的值?

演示地址:https://tsplay.dev/N9De8w

要求:根据提供的参数推断返回的类型(已实现)
问题:

  • 第二个参数类型是:Fn<D, any> | undefined
  • 我本意是可以不传这个参数
  • 但不传这个参数,拿到的结果就是 any

怎么才能让第二个参数不传也能得到结果

示例代码:


function tableSubmit <
  T extends FormInstance<any>,
  D extends DataFormType<T>,
  F extends Fn<D, any> | undefined,
  P extends FilterFormat<F, D>
>(
  form: T, filter?: F
): P {
  const params: D = form.get();
  const data  = filter !== undefined ? filter(params) : params;
  return data;
}

interface FormInstance<T> {
  data: T
  get: () => T;
}

type Fn<D, T extends any> = (data: D) => T 

type DataFormType<T> = T extends FormInstance<infer U> ? U : T

type FilterFormat<F, D> = F extends (data: D) => infer P ? P : D

const nameData = { name: "levi" };
const formName: FormInstance<{ name: string }> = {
  data: nameData,
  get: () => nameData
};

const ageData = { age: 18 };
const formAge: FormInstance<{ age: number }> = {
  data: ageData,
  get: () => ageData
};


const dataName = tableSubmit(formName, data => ({ ...data, site: 'sh' }));
const dataAge = tableSubmit(formAge, undefined);

// remove undenfind args is any
const dataError = tableSubmit(formAge);
阅读 681
avatarAI BotBETA

在 TypeScript 中,当你希望一个可选参数在未提供时仍然能够进行类型推断,你需要确保在类型定义中处理这种情况,使得当参数未提供时,不会退化为 any 类型。在你的例子中,你可以通过条件类型和一些类型技巧来实现这一点。

以下是修改后的代码,它允许第二个参数 filter 不传,并且仍然能够正确推断返回类型:

function tableSubmit <
  T extends FormInstance<any>,
  D extends DataFormType<T>,
  F extends Fn<D, any> | undefined = undefined
>(
  form: T, 
  filter?: F
): FilterFormat<F extends undefined ? (d: D) => D : F, D> {
  const params: D = form.get();
  const data = filter ? filter(params) : params as any;
  // 由于 TypeScript 在这里可能无法直接推断出 data 的类型,我们使用了一个断言(as any),
  // 但在返回类型中已经通过条件类型进行了约束,所以这是安全的。
  return data as FilterFormat<F extends undefined ? (d: D) => D : F, D>;
  // 这里的返回类型断言是为了确保 TypeScript 理解我们的意图,
  // 实际上由于条件类型的正确设置,这个断言是多余的,仅为了增强类型安全性。
}

interface FormInstance<T> {
  data: T
  get: () => T;
}

type Fn<D, T> = (data: D) => T;

type DataFormType<T> = T extends FormInstance<infer U> ? U : T;

type FilterFormat<F, D> = F extends (data: D) => infer P ? P : D;

const nameData = { name: "levi" };
const formName: FormInstance<{ name: string }> = {
  data: nameData,
  get: () => nameData
};

const ageData = { age: 18 };
const formAge: FormInstance<{ age: number }> = {
  data: ageData,
  get: () => ageData
};

const dataName = tableSubmit(formName, data => ({ ...data, site: 'sh' })); // { name: string; site: string; }
const dataAge = tableSubmit(formAge); // { age: number; }

解释

  1. 默认类型参数:在泛型参数 F 中,我们设置了 F extends Fn<D, any> | undefined = undefined,这意味着如果 F 没有被显式指定,它将默认为 undefined
  2. 条件类型:在返回类型中,我们使用条件类型 FilterFormat<F extends undefined ? (d: D) => D : F, D> 来处理 F 是否为 undefined 的情况。如果 Fundefined,我们用一个恒等函数 (d: D) => D 来代替 F,这样即使没有提供 filter 函数,返回类型仍然是 D
  3. 类型断言:在函数体内部,我们对 data 使用了 as any 类型断言,这仅仅是为了绕过 TypeScript 的类型检查器在当前作用域中的限制。由于返回类型已经通过条件类型正确设置了,所以这个断言在运行时不会改变任何东西,仅用于编译时的类型安全。

通过这种方式,你可以确保即使不传递第二个参数 filtertableSubmit 函数也能正确地推断并返回 D 类型的结果。

1 个回答

知道怎么做了

(推荐)方法 1:重载 https://tsplay.dev/w6D0Em

function tableSubmit <T, R>(form: FormInstance<T>): T 
function tableSubmit <T, R>(form: FormInstance<T>, filter: undefined): T 
function tableSubmit <T, R>(form: FormInstance<T>, filter: (data: T) => R): R
function tableSubmit <T, R>(form: FormInstance<T>, filter?: (data: T) => R): R | T {
  const params = form.get();
  const data  = filter !== undefined ? filter(params) : params;
  return data;
}

interface FormInstance<T> {
  data: T
  get: () => T;
}


const nameData = { name: "levi" };
const formName: FormInstance<{ name: string }> = {
  data: nameData,
  get: () => nameData
};

const ageData = { age: 18 };
const formAge: FormInstance<{ age: number }> = {
  data: ageData,
  get: () => ageData
};


const dataName = tableSubmit(formName, data => ({ ...data, site: 'sh' }));
const dataAge = tableSubmit(formAge, undefined);

// remove undenfind args is any
const dataError = tableSubmit(formAge);

方法 2:类型推导 https://tsplay.dev/WPQG5w

function tableSubmit <T, R extends (((data: T) => any) | undefined)>(form: FormInstance<T>, filter?: R) {
  const params = form.get();
  const data  = filter !== undefined ? filter(params) : params;
  return data as undefined extends R ? T : R extends (...args: any) => any ? ReturnType<R> : T 
}

interface FormInstance<T> {
  data: T
  get: () => T;
}


const nameData = { name: "levi" };
const formName: FormInstance<{ name: string }> = {
  data: nameData,
  get: () => nameData
};

const ageData = { age: 18 };
const formAge: FormInstance<{ age: number }> = {
  data: ageData,
  get: () => ageData
};


const dataName = tableSubmit(formName, data => ({ ...data, site: 'sh' }));
const dataAge = tableSubmit(formAge, undefined);

const dataError = tableSubmit(formAge);

推荐用重载,非重载会把 void 改造成 undefined

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进