reactjs - 是否可以使用对象类型定义来构造新的数组/元组类型?

标签 reactjs typescript

如果我有一个现有的类型定义:

type Person = {
  name: string;
  age: number;
  sayHello: (greeting: string) => void;
}

是否有可能构建一个由该定义的键和类型组成的类型化数组/元组?

例如,如果我想要这样的类型:

type PropAndTypes = ( 
  ['name',string] | 
  ['age',number]  | 
  ['sayHello', (arg: string) => void]
)[];

这能以某种方式从现有的类型定义中生成吗?

对于不太深奥的示例,特定的用例将是在给定链接组件的预期 Prop 的情况下帮助为路由库(ui-router)创建类型安全的状态声明。

示例状态声明有一个组件字段,以及将传递给组件的解析。

const exampleStateDefinition = {
  name: 'example.state',
  url: '/:name/:age',
  component: ExampleComponent,
  resolve: [
    {
      token: 'age' as const,
      deps: [Transition],
      resolveFn: (trans: Transition) => trans.params<Person>().age,
    },
    {
      token: 'name' as const,
      deps: [Transition],
      resolveFn: (trans: Transition) => trans.params<Person>().name,
    },
    {
      token: 'sayHello' as const,
      deps: [Transition],
      resolveFn: () => (greeting: str) => console.log(greeting),
    },
  ],
};

我想生成如下类型定义:

type stateDeclaration = {
    resolve: ResolveTypes[];
}

ResolveTypes 在哪里

type ResolveTypes = { token: 'name', resolve: string } | { token: 'age', resolve: number }; 

最佳答案

下面是我为“不太深奥的例子”(playground) 想出的内容:

const ExampleComponent = (props: { hey: string }) => ({ hey: props.hey });
interface Person {
  age: number;
  name: string;
}
interface Transition {
  params: <T>() => T;
}
const transition = { params: () => ({ age: 1, name: "aaa" }) };

const exampleStateDefinition = {
  name: "example.state",
  url: "/:name/:age",
  component: ExampleComponent,
  resolve: [
    {
      token: "age" as const,
      deps: [transition],
      resolveFn: (trans: Transition) => trans.params<Person>().age,
    },
    {
      token: "name" as const,
      deps: [transition],
      resolveFn: (trans: Transition) => trans.params<Person>().name,
    },
    {
      token: "sayHello" as const,
      deps: [transition],
      resolveFn: () => (greeting: string) => console.log(greeting),
    },
  ],
};

type ResolverIn<T, R> = {
  token: T;
  resolveFn: R;
};
type ResolverOut<T, R> = {
  token: T;
  resolve: R;
};

type T1<T> = T extends Array<infer U>
  ? U extends ResolverIn<infer V, infer R>
    ? R extends (...args: any) => infer Ret
      ? ResolverOut<V, Ret>
      : never
    : never
  : never;

type ResolveTypes = T1<typeof exampleStateDefinition["resolve"]>;
// = ResolverOut<"age", number> | ResolverOut<"name", string> | ResolverOut<"sayHello", (greeting: string) => void>
// which is:
// { token: "age", resolve: number } | { token: "name", resolve: string } | { token: "sayHello", resolve: (greeting: string) => void }

type StateDeclaration = {
  resolve: ResolveTypes;
};

事情的实质是

type T1<T> = T extends Array<infer U>
  ? U extends ResolverIn<infer V, infer R>
    ? R extends (...args: any) => infer Ret
      ? ResolverOut<V, Ret>
      : never
    : never
  : never;

这里我首先使用条件来确保类型是数组并推断数组元素的类型,然后推断形状为ResolverIn。 (有点像 Pick<> ),然后推断 resolveFn 的返回类型函数(如 ReturnType<T> ,但我们刚刚推断出类型,因此我们需要再次 infer 以进一步将类型限制为函数)并最终生成我们想要的形状 ResolverOut<V, Ret> .

ResolveTypes 的类型这样就变成了:

ResolverOut<"age", number> |
ResolverOut<"name", string> |
ResolverOut<"sayHello", (greeting: string) => void>

其形状相当于:

{ token: "age"; resolve: number } |
{ token: "name"; resolve: string } |
{ token: "sayHello"; resolve: (greeting: string) => void }

此外,您的示例排除了返回值为函数的解析器类型,可以使用另一个条件将其过滤掉:

type T1<T> = T extends Array<infer U>
  ? U extends ResolverIn<infer V, infer R>
    ? R extends (...args: any) => infer Ret
      ? Ret extends (...args: any) => any
        ? never
        : ResolverOut<V, Ret>
      : never
    : never
  : never;

编辑: 现在,我没有机会测试这个,但要生成 StateDeclaration直接来自 typeof exampleStateDefinition ,你或许可以这样做:

type T2<T> = T extends { resolve: infer U } ? { resolve: T1<U> } : never;
type StateDeclaration = T2<typeof exampleStateDefinition>;

编辑 2:我能够更接近您在评论中用 this answer 澄清的内容它使用一个实用函数(它只按原样返回传递给它的数组)来强制传递给它的数组包含联合类型的所有元素。 Playground .

interface Person {
  age: number;
  name: string;
}
interface Transition {
  params: <T>() => T;
}

type ResolveType<T> = {
  [K in keyof T]: { token: K; resolveFn: (...args: any[]) => T[K] };
}[keyof T];

type ResolveTypes<T> = ResolveType<T>[]


function arrayOfAll<T>() {
  return function <U extends T[]>(array: U & ([T] extends [U[number]] ? unknown : 'Invalid')) {
    return array;
  };
}

interface CustomStateDeclaration<T> {
  name: string;
  url: string;
  component: any;
  resolve: ResolveTypes<T>;
}

type ExampleComponentProps = {
  age: number;
  name: string;
  sayHello: (greeting: string) => string;
};

const arrayOfAllPersonResolveTypes = arrayOfAll<ResolveType<ExampleComponentProps>>()
// passes
const valid = arrayOfAllPersonResolveTypes([
  {
    token: "age" as const,
    resolveFn: (trans: Transition) => trans.params<Person>().age,
  },
  {
    token: "name" as const,
    resolveFn: (trans: Transition) => trans.params<Person>().name,
  },
  {
    token: "sayHello" as const,
    resolveFn: () => (greeting: string) => `Hello, ${greeting}`,
  },
])

// error; missing the "sayHello" token
const missing1 = arrayOfAllPersonResolveTypes([
  {
    token: "age" as const,
    resolveFn: (trans: Transition) => trans.params<Person>().age,
  },
  {
    token: "name" as const,
    resolveFn: (trans: Transition) => trans.params<Person>().name,
  }
])

// error; "name" token's resolveFn returns a number instead of a string
const wrongType = arrayOfAllPersonResolveTypes([
  {
    token: "age" as const,
    resolveFn: (trans: Transition) => trans.params<Person>().age,
  },
  {
    token: "name" as const,
    resolveFn: (trans: Transition) => 123,
  },
  {
    token: "sayHello" as const,
    resolveFn: () => (greeting: string) => `Hello, ${greeting}`,
  },
])

可以尝试创建一个类型来执行效用函数所做的事情,或者创建一个状态定义工厂/构造函数,所有状态声明都需要使用(可能由符号强制执行)创建,它使用该效用函数。

关于reactjs - 是否可以使用对象类型定义来构造新的数组/元组类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65711843/

相关文章:

javascript - 为什么 React 源代码是用 JavaScript 而不是 TypeScript 编写的,但仍然有类型?

javascript - 无法使用 webpack 将 NODE_ENV 设置为生产环境

html - 在 react.js 中的表内形成

javascript - React 从生成的子组件访问父方法

javascript - 类型 '{}' 不可分配给类型 'Pool' 。类型 'config' 中缺少属性 '{}'

angularjs - Angular2 View Child未定义错误

reactjs - react 来自 app.js 文件的 native 更改未反射(reflect)在世博会上

javascript - 如何通过nativescript-drop-down插件实现listview中数据的按需排序?

javascript - 数组自动更改值

javascript - 有什么方法可以使用拖放以 Angular 上传 .ply、.STL、.obj 文件吗?