我正在试图解决一个问题,迫使框架中的某一"模式",因为缺乏一个更好的词,我正在做的.所讨论的框架是Redux store Composer(给予或接受)的一种形式.

TS操场链接:

  1. full complex,所有类型都包括在内(这是我需要的,但它很棒)
  2. Siplified(最小可重现示例-显示相同问题)

要点如下:

我有一个函数,它被用来为名为"createBundle"的框架创建一个"包"或一个插件.这个函数的类型是BundleInitializer,这是一个复杂的类型,但可以说这个函数的返回类型是Bundle类型.

其目标是允许开发人员调用"createBundle"函数,然后该函数将获得智能感知支持,并允许更容易地遵循该函数对其接收的参数所需的模式.

BundleInitializer签名的语义如下(为清楚起见进行了简化):

type BundleInitializer = <
  Name extends string,
  Selectors extends Record<string, Selector>, //
>(
  content: {
    name: Name
    selectors: Selectors
  }
) => Bundle<Name, Selectors>

其中 Select 器的类型是(同样为了简单起见)Selector = () => unknown.这将允许我们传递一个类似以下形式的参数:

const arg = {
   name: "Example",
   selectors: {
      foo: () => {},
      bar: () => {}
   }
}

但我的目标是强制 Select 器的命名遵循select${Capitalize<string>}的模式.因此,我try 使用以下类型来实现这一点(后面的类型都在TS操场链接的"简化"版本中):

type Selector = <S>(state: S) => unknown


interface Bundle<
  Name extends string,
  Selectors extends BundleSelectors<Record<string, any>>,
> {
  name: Name
  selectors: Selectors
}

type BundleInitializer = <
  Name extends string,
  Selectors extends BundleSelectors<Record<string, any>>,
>(
  content: {
    name: Name
    selectors: Selectors
  }
) => Bundle<Name, Selectors>


declare const Init: BundleInitializer

const bun = Init({
  name: "Foo", selectors: {
    wrongname: 'wrongtype',                 // CASE 1 (name incorrect, type incorrect)
    selectCorrectName: 'wrongtype',         // CASE 2 (name correct, type incorrect)
    wrongName: () => { },                   // CASE 3 (name incorrect, type correct)
    selectCorrectAll: () => { },            // CASE 4 (name correct, type correct)
  }
})

我们应该如何定义BundleSelectors类型以确保涵盖所有4种情况, 我试过这两个版本,每个版本都有自己的问题,但都不能完全工作

版本1

type BundleSelectors<T> = { [I in keyof T]: Selector }

版本2

此处缺少类型参数,如果要判断类型,只需添加它即可,它已不再使用,但其他类型需要更新以反映

type BundleSelectors = Record<`select${string}`, Selector>

这两种做法都没有产生预期的效果.

推荐答案

一种方法是定义BundleInitializer,以便对应于selectorsgeneric类型参数递归为constrained,从而以某种方式拒绝任何与`select${string}`不匹配的属性键.这将涉及mapping类型,以便具有良好键的属性具有类型Selector,而具有不良键的属性具有类似the impossible never type的类型,该类型与可能传入的任何值都不兼容.让我们调用类型BadKey<K>,它接受类型K的键并产生不兼容的类型.

那么BundleInitializer将被定义为

type BundleInitializer = <
  N extends string,
  S extends { [K in keyof S]: K extends `select${string}` ? Selector : BadKey<K> }
>(content: { name: N; selectors: S }) => Bundle<N, S>

你会得到你想要的行为:

const bun = Init({
  name: "Foo", selectors: {
    wrongname: 'wrongtype',                 // error
    selectCorrectName: 'wrongtype',         // error
    wrongName: () => { },                   // error
    selectCorrectAll: () => { },            // okay
  }
})

因此,剩下的就是定义BadKey.我们可以简单地将其定义为never

type BadKey<K extends PropertyKey> = never;

但是错误消息只是说该属性不是预期的never,这是令人困惑的.理想情况下,TypeScrip将提供一个"无效"类型,其类型类似于never,但它提供用户友好的错误消息,但TypeScrip目前没有这样的功能.microsoft/TypeScript#23689美元有一个开放的功能需求.如果它存在,也许我们可以写

type BadKey<K extends PropertyKey> = Invalid<
  `You provided a key of "${Exclude<K, symbol>}" \
  but the keys all need to be of the form \`select${string}\``
>;

但事实并非如此.目前,我们所能做的就是

type BadKey<K extends PropertyKey> = `You provided a key of "${Exclude<K, symbol>}" \
  but the keys all need to be of the form \`select${string}\``;

并依赖于这样一个事实,即无论传入的是什么属性值,都不太可能是那个错误字符串.因此,我们会收到如下错误:

Type '() => void' is not assignable to type 
  '`You provided a key of "wrongName" but the keys all 
  need to be of the form \`select${string}\``'.(2322)

希望这足以让人们了解该做什么.

Playground link to code

Typescript相关问答推荐

net::BER_RECTION_REUSED和Uncatch使用Axios和TanStack-Query useMutation时未捕获错误(DelivercMutation)

有没有办法适当缩小类型?

将props传递给子组件并有条件地呈现它''

如何在第一次加载组件时加载ref数组

输入元素是类型变体的对象数组的正确方法是什么?'

TypeScrip 5.3和5.4-对`Readonly<;[...number[],字符串]>;`的测试版处理:有什么变化?

如何将所有props传递给React中的组件?

为什么我的条件类型不能像我预期的那样工作?

在分配给类型的只读变量中维护const的类型

在另一个类型的函数中,编译器不会推断模板文字类型

如果数组使用Reaction-Hook-Form更改,则重新呈现表单

为什么ESLint会抱怨函数调用返回的隐式`any`?

try 呈现应用程序获取-错误:动作必须是普通对象.使用自定义中间件进行MySQL操作

Angular material 无法显示通过API获取的数据

TypeScrip:使用Cypress测试包含插槽的Vue3组件

VUE 3类型';字符串';不可分配给类型';参考<;字符串&>

如何使用useSearchParams保持状态

JSX.Element';不可分配给类型';ReactNode';React功能HOC

React中的效果挂钩在依赖项更新后不会重新执行

如何在Typescript 中定义具有相同类型的两个泛型类型的两个字段?