【问题标题】:Infer type from parent props从父道具推断类型
【发布时间】:2021-11-20 06:14:27
【问题描述】:

我想知道在 TypeScript 中是否有可能根据父项的 prop 推断类型。
让我用一个例子更好地解释一下:假设我有<Parent><Child> 组件,以及一个接口IMyComplexObject

interface IMyComplexObject {
    name: string;
    surname: string;
}

const MyApp = (props) => {
    const [obj, setObj] = React.useState<IMyComplexObject>({name: 'Foo', surname: 'Bar'});

    return (
        <Parent context={obj}>
            <Child value={'name'} />
        </Parent>
    );
}

所以,我要问的是:当我写&lt;Child value={'name'} /&gt; 时,是否可以推断出value 属性的接受值是'name' | 'surname',这是obj 的键?

我知道,如果我只有一个组件,事情会很容易。假设:

interface IChildProps<T> {
    ctx: T;
    value: keyof T;
}

- - -

return <Child ctx={obj} value={'name'} />

然后,智能感知器会告诉我哪些是可接受的值:

但是,当定义上下文的是 &lt;Parent&gt; 组件而不是组件本身时,事情会变得更加棘手。

编辑: 为了回答@captain-yossarian 的提议,&lt;Child&gt; 上的泛型类型的问题是我最终会得到如下代码:

<Parent context={obj}>
    <Child<IMyComplexObject> value={...} />
    <Child<IMyComplexObject> value={...} />
    <Child<IMyComplexObject> value={...} />
    ...
</Parent>

泛型太多(每个孩子一个)。更重要的是,传递给&lt;Parent&gt;obj 的类型与传递给&lt;Child&gt;s 组件的泛型类型之间没有实际联系。
因此,例如,这样的事情仍然是“合法的”:

interface IObj_1 {
    age: number;
}

interface IObj_2 {
    name: string;
}


const [obj1, setObj1] = React.useState<IObj_1>({age: 7});
const [obj2, setObj2] = React.useState<IObj_2>({name: 'Foo'});

return (
    <Parent context={obj1}>
        <Child<IObj_2> value={'name'} />
    </Parent>
);

【问题讨论】:

  • 我不确定这种方法在 React 中是否真的有意义,理论上组件应该尽可能自包含,为什么要这样做? (只是想知道这是否是一个 XY 问题)
  • 理想情况下,制作一组要一起使用的组件。我简化了这个例子,但我想,如果我使用组件的真实名称,事情会更容易理解。假设父级称为&lt;MyForm&gt;,子级称为&lt;MyTextInput&gt;:目标是在&lt;MyForm&gt; 中定义一个上下文,并且每个子级都可以利用该上下文。

标签: reactjs typescript


【解决方案1】:

是的,有可能。

您需要为子组件提供显式的通用参数。

<Child<MyComplexObject> value={"name"} >

和子实现:

const Child=<T,>(props: {value: keyof T})=>null

或者

const Child=<T,>(props: {ctx: T, value: keyof T})=>null

更新

我认为最简单的方法是为Parent 提供额外的道具:itemscallback

items - IMyComplexObject 的键数组。 items 本身是一个原始值,它直接传递给Childvalue 属性

callback - Array.prototype.map 回调。

import React, { useState } from 'react'


interface IMyComplexObject {
  name: string;
  surname: string;
}

type ParentProps<Context> = {
  context: Context;
  items: Array<keyof Context>;
  callback: (prop: keyof Context) => JSX.Element
}

type ChildProps = { value: string }

const Parent = <Context,>({ items, callback }: ParentProps<Context>) =>
  <>
    {items.map(callback)}
  </>

const Child = ({ value }: ChildProps) => <p>{value}</p>

const MyApp = () => {
  const [context, setContext] = useState<IMyComplexObject>({ name: 'Foo', surname: 'Bar' });

  return (
    <>
      <Parent
        context={context}
        items={['name']}
        callback={
          (prop) => <Child value={prop} />
        }
      />

      <Parent
        context={context}
        items={['hello']} /** expected error */
        callback={
          (prop) => <Child value={prop} />
        }
      />
    </>
  );
}

Playground

【讨论】:

  • 谢谢。我知道这种可能的解决方案。问题在于该类型不是由&lt;Parent&gt; 推断的,而是从&lt;Child&gt; 本身推断的。因此,我会被泛型淹没。请参阅问题中的编辑以获得更好的解释(需要多行代码)。
  • 好的。理解。你需要做一个小的重构。父组件应该将子组件渲染为函数。它将在 Parent 道具和 Children 道具之间创建连接。请看我的文章catchts.com/currying-components。我不是严格相关的,但可能会给你一个想法。由于我没有键盘,我稍后会为您提供示例
  • 好吧,我明白你的意思:) 让&lt;Parent&gt; 返回类似props.children(props.context) 的内容,然后在我渲染&lt;Parent&gt; 的地方使用类似&lt;Parent&gt;{ctx =&gt; &lt;Child name={ /* Here the type of ctx is inferred */} /&gt;&lt;/Parent&gt; 的内容。这肯定是一个更好的解决方案。如果不能直接推断最好。
  • 是的,就是这样。
猜你喜欢
  • 2022-10-14
  • 1970-01-01
  • 2023-01-15
  • 2020-03-22
  • 1970-01-01
  • 2017-03-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多