TypeScript:如何将类型信息从父级传递给子级?

鉴于以下Form及其(不受控制的)Input子项:

<Form initialValues={{ firstName: "", lastName: "", age: "" }}>
  <Input label="First name" name="firstName" />
  <Input label="Last name" name="lastName" />
  <Input label="Age" name="age" />
</Form>

我希望Input's nameprop 是 type "firstName" | "lastName" | "age"。这种类型应该派生自Form's initialValues

实现这一目标的最干净的方法是什么?


注意:在一般情况下,表单输入组件应该是 tree-shakeable。

回答

这条信息无法自动推断。您必须以一种或另一种方式手动提供类型。临时解决方案如下:

function App() {
  const initialValues = {
    firstName: 'string',
    lastName: 'string',
    age: 'string',
  }

  const MyInput: React.FC<{ label: string, name: keyof typeof initialValues }> = Input
  return <Form initialValues={initialValues}>
    <MyInput label="First name" name="firstNameWRONG" /> // error
    <MyInput label="Last name" name="lastName" />
    <MyInput label="Age" name="age" />
  </Form>
}

我想谈谈为什么这是不可能的。

1.组件声明自己的协议

我猜您的思维模型是将子组件视为父组件的“参数”,因此从父组件到子组件提出某种“要求”是合理的。

我不会说这样的观点是完全错误的,实际上父子组件可以以耦合方式编写,但它在反应中并不惯用。

理想情况下,反应组件应该通过props type. 您可能将组件视为“服务”,服务消费者有责任遵守协议,而不是相反。

2. TS 类型系统的限制

转译为 JS,这样的结构变成:

function App() {
  const initialValues = {
    firstName: 'string',
    lastName: 'string',
    age: 'string',
  }

  const MyInput: React.FC<{ label: string, name: keyof typeof initialValues }> = Input
  return <Form initialValues={initialValues}>
    <MyInput label="First name" name="firstNameWRONG" /> // error
    <MyInput label="Last name" name="lastName" />
    <MyInput label="Age" name="age" />
  </Form>
}

首先,如果要引发任何类型错误,则应按行引发parentElement,而不是childElement. 违反的是父组件的协议,该协议规定“子组件name应该是keyof initValue”。而这种验证是由React.createElement函数根据其参数完成的。

其次,从类型系统的角度来看,如果我们能够从 推断出childProps的类型parentProps,那么解析过程应该是这样的:

1. let `Parent` be generic type of form
   Component<T, E<_>> = (props: { initValues: T, children: E<keyof T> }) => Element<any>
2. let `Child` be generic type of form
   Component<K> = (props: { name: K }) => Element<K>
3. from `childElement = React.createElement(Child, childProps))`
   we know `childElement` is type `Element<string>`
4. from `parentElement = React.createElement(Parent, parentProps, childElement)`
   we know about `T` and `E = Element` and `_ = keyof T`
5. now we need to allow prioritze `E<_>` rule over `Element<string>`, thus override `childElement` from `Element<string>` to `Element<keyof T>`

为了让这样的类型系统工作,我们既需要支持更高级的类型参数E<_>,也需要某种类型操作的优先级。

实际上,我们需要指定childElement尚未解决,但仍处于挂起状态的Element<_>,然后让下一个解决步骤填写_部分。

另外,我们的意思不是这样的,

Element<T>.fill(arg: T)

但我们的意思是,

fill<T>(arg0: T, arg1: Element<T>)

AFAIK,从来没有一个类型系统支持这种行为,更不用说 TS 甚至不支持更高级的类型。

----

更新

我认为值得一提的是,理论上可以从Formabout namepropInput不遵守协议引发类型错误。但是它不能用 JSX 来完成,只能通过React.createElement.

这是因为 TS 为所有 JSX 创建的元素分配了特殊的内置接口JSX.Element。并且 react 已经将它扩展到了扩展React.ReactElement<any, any>。这any件事有效地最大化了props所有元素的协议,使任何限制成为不可能。

我试图找到解决方法,但不幸的是没有找到。你能得到的最好的是评论中提供的链接中的建议。


以上是TypeScript:如何将类型信息从父级传递给子级?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>