typescript - 如何将可区分联合中的对象映射到可以调用它们的函数?

标签 typescript

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

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

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

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

使用 2 种类型的字段只是为了让示例变小,但是这段代码的优点是泛型方法不需要知道字段类型,它将决定委托(delegate)给 fieldMapping 提供的映射.

我正在尝试用 TypeScript 编写类似的东西。但我无法弄清楚如何让这些类型工作并仍然使用一个对象来提供 type 和要委托(delegate)给的函数之间的映射。

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

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)
  }
}

最佳答案

恐怕您将不得不使用 type assertion以避免此处的代码重复。 TypeScript 的类型系统对这些没有很好的支持 "correlated record types"或依赖于联合不独立的两个联合类型值的交互的任何操作。

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

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是字符串长度函数。

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

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

关于 renderFn 的类型,您对编译器撒了一些谎。 ...与其说它会接受任何旧参数(例如,renderFn(123) 将根据需要失败),但足以让它允许这样做:

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

所以你要小心。

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

Link to code

关于typescript - 如何将可区分联合中的对象映射到可以调用它们的函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56781010/

相关文章:

typescript - Typescript 的泛型类型别名 Exclude<T,U> 到底是如何工作的?

TypeScript 泛型函数参数和返回类型推断

javascript - Void 类型不可分配给 Boolean 类型(React/Typescript)

Angular 10 : Unable to write a reference

angular - 如何按特定顺序运行测试 - Playwright YAML 文件

node.js - Webpack 中的 Typescript 定义文件

reactjs - 如何正确定义样式组件(对于 TypeScript)的引用(React.RefObject<StyledComponentClass>)?

typescript - 为什么我的属性注入(inject)尝试除了未定义之外没有注入(inject)任何东西?

javascript - 如何在typescript中通过ssdp发送http请求

javascript - react + typescript : Property * is missing in type *