如何定义文字对象以使用泛型存储不同的属性 [英] How to define a literal object to store different properties with generics

查看:28
本文介绍了如何定义文字对象以使用泛型存储不同的属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在文字对象中存储一些反应组件.而且这些组件的props是不一样的.

interface ComponentMap

{[key: string]: React.JSXElementConstructor<P>}const cMap: ComponentMap = {}接口 AProps {str:字符串}接口 BProps {n:数字}cMap['a'] = (props: AProps) =>{/* 代码 */}cMap['b'] = (props: BProps) =>{/* 代码 */}//可能有 c、d、e 等组件被赋值到 cMap

目前我使用

来避免类型错误.但是当我从 cMap 获取一个组件时,它的 props 类型也是 any.

const CompA = cMap['a']const CompB = cMap['b']<CompA/><CompB/>//没有类型错误

有没有办法保持道具的CompA/B类型?如何正确定义ComponentMap?请帮忙,谢谢!

解决方案

当你想强制一个变量扩展一个特定类型而不扩展那个变量的类型时,然后您需要通过函数创建变量.

type BaseMap = Record;const createComponentMap = (map: M): M =>地图;const cMap = createComponentMap({a:(道具:AProps)=>{/* 代码 */返回空值;},b:(道具:BProps)=>{/* 代码 */返回空值;}})const ComponentA = cMap.a;<ComponentA str=a"/>//好的<组件A/>//错误:属性str"在类型{}"中缺失,但在AProps"类型中需要


<块引用>

更多未知组件会动态分配到cMap中

这部分让它变得更有趣.您可以在这里创建一些奇特的东西,其中地图具有添加新对象的方法.简单的解决方案就是在您添加任何内容时创建一个新的地图对象,因为您总是想更改地图的类型.下面是我们如何添加第三个组件 c:

const nextMap = createComponentMap({...cMap,c:(道具:CProps)=>{/* 代码 */返回空值;}});


以下是链接方法的工作原理.add 函数采用像 {c: ComponentC} 这样的对象比单独的键和值参数更容易.

interface ComponentMap{地图:M;add(添加:T):ComponentMap(map: M): ComponentMap=>({地图,add(添加:T):ComponentMap{/* 代码 */返回空值;},b:(道具:BProps)=>{/* 代码 */返回空值;}})const ComponentA = cMap.get('a')//或 cMap.map.a;const nextMap = cMap.add({c:(道具:CProps)=>{/* 代码 */返回空值;}});


以下是它如何将 keycomponent 作为单独的参数使用:

interface ComponentMap{地图:M;add>(key: K, component: C): ComponentMap(map: M): ComponentMap=>({地图,add>(key: K, component: C): ComponentMap {/* code */return null; }).add("b", (props: BProps) => {/* code */return null; }).add("c", (props: CProps) => {/* code */return null; });

I want to store some react components in a literal object. And these components' props are different.

interface ComponentMap<P = any> {
  [key: string]: React.JSXElementConstructor<P>
}

const cMap: ComponentMap = {}

interface AProps {
  str: string
}

interface BProps {
  n: number
}

cMap['a'] = (props: AProps) => { /* code */ }
cMap['b'] = (props: BProps) => { /* code */ }
// there may be c, d, e and other components assigned into cMap

Currently I use <P = any> to avoid type errors. But when I get a component from cMap, its props type is also any.

const CompA = cMap['a']
const CompB = cMap['b']

<CompA /> 
<CompB />
// no type errors 

Is there any way to keep the props' type of CompA/B? How to correctly define ComponentMap? Please help, thanks!

解决方案

When you have a situation where you want to enforce that a variable extends a particular type without widening the type of that variable, then you need to create the variable through a function.

type BaseMap = Record<string, ComponentType<any>;

const createComponentMap = <M extends BaseMap>(map: M): M => map;

const cMap = createComponentMap({
  a: (props: AProps) => { /* code */ return null; },
  b: (props: BProps) => { /* code */ return null; }
})

const ComponentA = cMap.a;

<ComponentA str="a"/> // ok
<ComponentA/> // error: Property 'str' is missing in type '{}' but required in type 'AProps'


more unknown components will be assigned into cMap dynamically

This part makes it more interesting. You could create something fancy here where the map had methods to add new objects. The simple solution is just to create a new map object whenever you add anything because you always want to change the type of the map. Here's how we can add a third component c:

const nextMap = createComponentMap({
  ...cMap,
  c: (props: CProps) => { /* code */ return null; }
});


Here's how it works with the chaining approach. It's easier for the add function to take an object like {c: ComponentC} than separate key and value arguments.

interface ComponentMap<M extends BaseMap> {
  map: M;
  add<T extends BaseMap>(added: T): ComponentMap<M & T>;
  get<K extends keyof M>(key: K): M[K];
}

const createComponentMap = <M extends BaseMap>(map: M): ComponentMap<M> => ({
  map,
  add<T extends BaseMap>(added: T): ComponentMap<M & T> {
    return createComponentMap({
      ...map,
      ...added
    })
  },
  get<K extends keyof M>(key: K): M[K] {
    return map[key];
  }
});

const cMap = createComponentMap({
  a: (props: AProps) => { /* code */ return null; },
  b: (props: BProps) => { /* code */ return null; }
})

const ComponentA = cMap.get('a') // or cMap.map.a;

const nextMap = cMap.add({
  c: (props: CProps) => { /* code */ return null; }
});


Here's how it works with the key and component as separate arguments:

interface ComponentMap<M extends BaseMap> {
  map: M;
  add<K extends string, C extends ComponentType<any>>(key: K, component: C): ComponentMap<M & Record<K, C>>;
  get<K extends keyof M>(key: K): M[K];
}

const createComponentMap = <M extends BaseMap>(map: M): ComponentMap<M> => ({
  map,
  add<K extends string, C extends ComponentType<any>>(key: K, component: C): ComponentMap<M & Record<K, C>> {
    return createComponentMap({
      ...map,
      [key]: component
    })
  },
  get<K extends keyof M>(key: K): M[K] {
    return map[key];
  }
});

const cMap = createComponentMap({})
  .add("a", (props: AProps) => { /* code */ return null; })
  .add("b", (props: BProps) => { /* code */ return null; })
  .add("c", (props: CProps) => { /* code */ return null; });

这篇关于如何定义文字对象以使用泛型存储不同的属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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