TypeScript:如何将可区分联合中的对象映射到可以调用它们的函数? [英] TypeScript: How to map objects in a discriminated union to functions they can be called with?

查看:22
本文介绍了TypeScript:如何将可区分联合中的对象映射到可以调用它们的函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 vanilla JS 中,我可以编写一些如下所示的代码:

In vanilla JS, I'm able to write some code that looks something like the following:

function renderTextField(props) { }
function renderSelectField(props) { }

const fieldMapping = {
    text: renderTextField,
    select: renderSelectField,
};

function renderField(field) {
    const renderFn = fieldMapping[field.type];
    renderFn(field.data);
}

使用 2 种类型的字段只是为了让示例保持较小,但是这段代码的好处是泛型方法不需要知道字段的类型,它将决定委托给 <提供的映射代码>fieldMapping.

Using 2 types of fields just to keep the example small, but the nice thing about this code is that the generic method doesn't need to know about the type of field, and it delegates the decision to the mapping provided by fieldMapping.

我正在尝试在 TypeScript 中编写类似的东西.但我不知道如何让类型工作并仍然使用对象来提供 type 和要委托给的函数之间的映射.

I'm trying to write something similar in TypeScript. But I can't figure out how to get the types to work and still use an object to provide a mapping between type and the function to delegate to.

我意识到我可以使用 switch 语句或条件而不是对象来映射事物,但如果可能的话,我更愿意这样做.

I realise that I could use a switch statement or conditionals instead of an object to map things, but I would prefer to do it this way if at all possible.

type TextFieldData = { value: string }
type TextField = { type: 'text', data: TextFieldData }
type SelectFieldData = { options: string[], selectedValue: string }
type SelectField = { type: 'select', data: SelectFieldData }
type FormField = TextField | SelectField

function renderTextField(props: TextFieldData) {}
function renderSelectField(props: SelectFieldData) {}

const fieldMapping = {
  text: renderTextField,
  select: renderSelectField,
}

// This won't work!
function renderFieldDoesNotWork(field: FormField) {
  const renderFn = fieldMapping[field.type]

  // Type 'TextFieldData' is missing the following properties from type 'SelectFieldData': options, selectedValue
  renderFn(field.data)
}

// This works
function renderFieldWorks(field: FormField) {
  if (field.type === 'text') {
    const renderFn = fieldMapping[field.type]
    renderFn(field.data)
  } else if (field.type === 'select') {
    const renderFn = fieldMapping[field.type]
    renderFn(field.data)
  }
}

推荐答案

恐怕您将不得不使用 类型断言 以避免此处的代码重复.TypeScript 的类型系统对这些相关记录类型" 没有很好的支持或任何依赖于两个联合类型值的交互的操作,其中联合不是独立的.

I'm afraid that you're going to have to use a type assertion to avoid code duplication here. TypeScript's type system just doesn't have good support for these "correlated record types" or any manipulation that relies on the interaction of two union-typed values where the unions are not independent.

您已经了解了冗余代码输入switch语句的解决方法;这是不安全断言的解决方法:

You've already arrived at the redundant-code-in-switch-statement workaround; here's the unsafe-assertion workaround:

function assertNarrowFunction<F extends (arg: any) => any>(f: F) {
  return f as (arg: Parameters<F>[0]) => ReturnType<F>; // assert
}

这需要一个联合类型的函数,如 ((a: string)=>number) |((a: number)=>boolean) 并且不安全地将其缩小为一个函数,该函数接受其参数类型的并集并返回其返回类型的并集,例如 ((a: string | number) => string | number).这是不安全的,因为以前联合类型的函数可能类似于 const f = Math.random()<0.5 ?((a: string)=>a.length) : ((a: number)=>number.toFixed()),这肯定匹配((a: string | number) => string | number).我不能安全地调用 f(5) 因为也许 f 是字符串长度函数.

That takes a function of a union type like ((a: string)=>number) | ((a: number)=>boolean) and unsafely narrows it to a function which takes the union of its parameter types and returns the union of its return type, like ((a: string | number) => string | number). This is unsafe because a function of the former union type could be something like const f = Math.random()<0.5 ? ((a: string)=>a.length) : ((a: number)=>number.toFixed()), which definitely does not match ((a: string | number) => string | number). I can't safely call f(5) because maybe f is the string-length function.

无论如何,您可以在 renderFn 上使用这种不安全的缩小来消除错误:

Anyway, you can use this unsafe narrowing on renderFn to silence the error:

function renderFnAssertion(field: FormField) {
  const renderFn = assertNarrowFunction(fieldMapping[field.type]);
  renderFn(field.data); // okay
}

你在 renderFn 的类型上对编译器撒了一点谎…​​…并没有太多以至于它会接受任何旧参数(例如,renderFn(123) 会根据需要失败),但足以允许这样做:

You've lied to the compiler a bit about the type of renderFn... not so much that it will accept any old argument (e.g., renderFn(123) will fail as desired), but enough that it would allow this:

function badRenderFn(field1: FormField, field2: FormField) {
  const renderFn1 = assertNarrowFunction(fieldMapping[field1.type]);
  renderFn1(field2.data); // no error!!! ooops
}

所以你必须小心.

好的,希望有帮助;祝你好运!

Okay, hope that helps; good luck!

代码链接

这篇关于TypeScript:如何将可区分联合中的对象映射到可以调用它们的函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆