包装组件,将props传递给子组件的渲染函数

huangapple go评论58阅读模式
英文:

Wrapper component that passes props to children render function

问题

  1. Wrapper 组件中,当调用 children(rest) 时,编译器指示该参数可分配给类型 P 的约束,但 P 可能会用不同的子类型来实例化,从而不兼容。在这里可能会实例化与此不兼容的 P 的一种情况是,当你在其他地方将 Wrapper 组件使用时,传递了与 P 约束不匹配的属性。这可能导致编译器认为 rest 参数不符合 P 的约束。通过将 rest 强制转换为 P,你可以解决此问题,但需要确保在所有使用 Wrapper 组件的地方都符合 P 的约束。

  2. WrappedInput 组件中,编译器认为 props 对象(在子级渲染函数中)具有 childrentitle 属性,这可能是因为编译器无法确定 P 的确切类型,因此假设它可能包含这些属性。要解决这个问题,你可以为 WrappedInput 组件明确指定 P 的类型,以确保它不包含不必要的属性。例如,你可以这样做:

const WrappedInput = (inputProps: InputProps) => <Wrapper<InputProps>
    title="Hello, World"
    {...inputProps}
>{(props) => <input {...props} />}
</Wrapper>;

这样,你明确指定了 Wrapper 组件的 P 类型为 InputProps,以确保不会出现类型错误。

英文:

I'm trying to type a wrapper component that takes a title prop and a children prop that's a function. All the other props passed to the wrapper should be used as an argument when rendering the children:

import React, { ReactNode, InputHTMLAttributes } from &quot;react&quot;

type WrapperProps&lt;P extends object&gt; = {
    title: string
    children: (props: P) =&gt; ReactNode
} &amp; P

const Wrapper = &lt;P extends object&gt;({ title, children, ...rest }: WrapperProps&lt;P&gt;) =&gt; {
    return children(rest)
}

type InputProps = { type: string, name: string }

const WrappedInput = (inputProps: InputProps) =&gt; &lt;Wrapper
    title=&quot;Hello, World&quot;
    {...inputProps}
&gt;{(props) =&gt; &lt;input {...props} /&gt;}
&lt;/Wrapper&gt;

I can't understand two things:

  1. In the Wrapper component, when calling children(rest) the compiler indicates that the argument is assignable to the constraint of type 'P', but 'P' could be instantiated with a different subtype of constraint 'object'. What could P be instantiated with that would be incompatible here? I can cast rest as P but I'd like to understand what the issue is.

  2. In the WrappedInput component, the compiler thinks that the props object (in the children render function) has children and title properties. How can that be when I've explicitly said that the argument that children are being called with is of type P and P doesn't have those properties?

答案1

得分: 1

主要问题是,正如您在第二个问题中提到的,传递给渲染函数的props的类型是P,但作为泛型,P 用于捕获所有props,而不管它们如何分割和使用。

所以对于您的第一个问题,如果您考虑一种情况,您希望传递给children的props也包含一个标题字段,例如,并且该类型不一定是字符串,那么这些类型就不相关。

对于您的第二个问题,这也涉及到第一个问题,最快的方法,同时改变当前结构最少的方法,是将children参数的类型指定为Omit<P, "title"|"children">,如您可以在这里看到的那样。

这不会完全符合您的要求,泛型P 仍然具有属性childrentitle。您可以尝试说 P 扩展了一些不允许这些属性的东西,比如 P extends Omit<Record<string, unknown>, "title"|"children"> 来满足这个条件,但我相当确定在 TypeScript 中这不起作用,您不能指定扩展某个记录并否定某些字段,参考 https://github.com/microsoft/TypeScript/issues/31153

但是不要认为这是个大问题,如果P 具有所有属性,您只需从children渲染参数中省略它们即可。

type WrapperProps<P extends object> = {
    title: string;
    children: (props: Omit<P, "title"|"children">) => ReactNode;
} & P;

您可能还希望将 object 更改为类似于 Record<string, unknown> 这样的东西,因为 object 将接受诸如数组之类的东西,通常情况下,Record 更适合props。

您还可以更改您的类型,以使P 可以完全成为children的props,但然后您无法将它们扩展到主props类型。类似于这样:

type WrapperProps<P extends Record<string, unknown>> = {
  title: string;
  children: (props: P) => ReactNode;
  childrenProps: P;
};

然后,类型就不会存在不确定性,因为P 仅用作children函数的参数,并作为WrapperProps 上的自己的字段。

希望这有所帮助。

英文:

the main issue here is, as you mentioned in second question, the props passed to the render function is of type P but as a generic, P is being used to capture all the props, regardless of how they are split and used.

So for you first question, if you consider a situation where the props you want to be passed to the children also contain a title field for example, and that the type was not necessarily to be a string, then the types are not related.

For your second question, this also touched upon the first question, the quickest approach whilst changing least the current structure is to specify the type of children arg as `Omit<P, "title"|"children"> as you can see here.

This won't be exactly as you specified, the generic P will does still have properties children and title. You could try to say P extends something that does not allow those such as P extends Omit&lt;Record&lt;string,unknown&gt;, &quot;title&quot;|&quot;children&quot; to try and satisfy that condition but fairly sure that does not work in typescript, you can't specify to extend some record negating certain fields, linked to https://github.com/microsoft/TypeScript/issues/31153.

But don't think that's a big deal, it's fine if P has all the properties, and you can just omit them from children render args.

type WrapperProps&lt;P extends object&gt; = {
    title: string
    children: (props: Omit&lt;P,&quot;title&quot;|&quot;children&quot;&gt;) =&gt; ReactNode
} &amp; P

You probably also want to change object to something like Record&lt;string,unknown&gt; since object will accept things like arrays, Record is generally better for props.

You can also change your types so that P can be exactly the props for children but then you cannot spread them onto that main props type. Something like this

type WrapperProps&lt;P extends Record&lt;string, unknown&gt;&gt; = {
  title: string;
  children: (props: P) =&gt; ReactNode;
  childrenProps: P;
};

Then there is no uncertainty with the types as P is used only as args for children function and as it's own field on WrapperProps.

Hope it helps

huangapple
  • 本文由 发表于 2023年6月5日 06:50:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76402705.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定