英文:
Hide generic type prop on export
问题
以下是您要求的翻译部分:
I have a small set of components in which a Wrapper
is going to manipulate its children
(therefore referred to as Component
s by injecting a prop into each of the children via cloneElement
.
我有一组小组件,其中一个 Wrapper
将通过 cloneElement
向其 children
注入一个 prop 来操作它们(因此将其称为 Component
)。
The gotcha here is that Component
props are of a generic type. When I expose Component
on the code, I don't want one of its props to be on the signature because it will be automatically injected by the Wrapper
component.
问题在于 Component
的 props 是一种通用类型。当我在代码中公开 Component
时,我不希望其中的一个 prop 出现在签名中,因为它将由 Wrapper
组件自动注入。
I have a concise example which shows what I mean:
我有一个简洁的示例,可以说明我的意思:
types.ts
export type SomeObject = {
someKey: string;
};
type PropThatWillBeInjected<T extends SomeObject> = {
fn: (value: string) => T;
};
export type WannaBePropTypes = {
name: string;
};
export type PropTypes<T extends SomeObject> = PropThatWillBeInjected<T> &
WannaBePropTypes;
重要提示:PropTypes<T>
是 Component
预期的内容,但作为程序员,我希望 WannaBePropTypes
成为该组件的签名。
继续...
Component.tsx
function Component<T extends SomeObject>(props: PropTypes<T>) {
const { fn, name } = props;
const result = fn(name);
return <div>Hello, {result.someKey}</div>
}
export default Component;
Wrapper.tsx
function Wrapper(props: { children: ReactNode }) {
const { children } = props;
return (
<div id="wrapper">
{React.Children.map(
children as ReactElement<PropTypes<SomeObject>>,
(child, index) =>
cloneElement(child, {
...child.props,
fn: (value: string) => ({
someKey: `${value}-${index}`,
}),
})
)}
</div>
);
}
export default Wrapper;
As expected, when I try to use these components as the following, the code works but the compiler complains:
如预期的那样,当我尝试像下面这样使用这些组件时,代码可以工作,但编译器会发出投诉:
<Wrapper>
<Component name="Alice" />
<Component name="Bob" />
</Wrapper>
Property 'fn' is missing in type '{ name: string; }' but required in type 'PropThatWillBeInjected<SomeObject>'.(2741)
Is there a way to cast Component
so I don't need to pass fn
manually? I know there's a way when the prop types is not generic...
有没有一种方法可以将 Component
转换,以便我不需要手动传递 fn
?我知道当 prop 类型不是通用的时候有一种方法...
What I've tried:
- Making
fn
optional: works, but this is not the solution I'm looking for; - Wrapping
Component
with another component and passing anoop
toComponent
: works, but I don't want to create this unnecessary wrapper;
我尝试过的方法: - 使
fn
成为可选项:可以工作,但这不是我寻找的解决方案; - 使用另一个组件包装
Component
并将noop
传递给Component
:可以工作,但我不想创建这个不必要的包装器;
A playground with this sample code: StackBlitz(包含此示例代码的在线演示)。
英文:
I have a small set of components in which a Wrapper
is going to manipulate its children
(therefore referred to as Component
s by injecting a prop into each of the children via cloneElement
.
The gotcha here is that Component
props are of a generic type. When I expose Component
on the code, I don't want one of its props to be on the signature, because it will be automatically injected by the Wrapper
component.
I have a concise example which shows what I mean:
types.ts
export type SomeObject = {
someKey: string;
};
type PropThatWillBeInjected<T extends SomeObject> = {
fn: (value: string) => T;
};
export type WannaBePropTypes = {
name: string;
};
export type PropTypes<T extends SomeObject> = PropThatWillBeInjected<T> &
WannaBePropTypes;
Important: PropTypes<T>
is what Component
expects, but as a programmer, I want WannaBePropTypes
to be the signature of this component.
Moving on...
Component.tsx
function Component<T extends SomeObject>(props: PropTypes<T>) {
const { fn, name } = props;
const result = fn(name);
return <div>Hello, {result.someKey}</div>;
}
export default Component;
Wrapper.tsx
function Wrapper(props: { children: ReactNode }) {
const { children } = props;
return (
<div id="wrapper">
{React.Children.map(
children as ReactElement<PropTypes<SomeObject>>,
(child, index) =>
cloneElement(child, {
...child.props,
fn: (value: string) => ({
someKey: `${value}-${index}`,
}),
})
)}
</div>
);
}
export default Wrapper;
As expected, when I try to use these components as the following, the code works but the compiler complains:
<Wrapper>
<Component name="Alice" />
<Component name="Bob" />
</Wrapper>
> Property 'fn' is missing in type '{ name: string; }' but required in type 'PropThatWillBeInjected<SomeObject>'.(2741)
Is there a way to cast Component
so I don't need to pass fn
manually? I know there's a way when the prop types is not generic...
What I've tried:
- Making
fn
optional: works, but this is not the solution I'm looking for; - Wrapping
Component
with another component and passing anoop
toComponent
: works, but I don't want to create this unnecessary wrapper;
A playground with this sample code: StackBlitz
答案1
得分: 1
如果我正确理解您的问题,您希望将 Component
作为 <Component name="Alice" />
进行调用,同时有一些内部逻辑来处理两种情况:fn
被传递和未被传递的情况。如果是这样,您可以创建不必要的类型(而不是不必要的包装),这些类型将是 WannaBePropTypes
或完整的 props 之一。这类似于您尝试的第一种和第二种的组合:
type FullProps<T extends SomeObject> = PropThatWillBeInjected<T> & WannaBePropTypes;
type PropTypes<T extends SomeObject> = FullProps<T> | WannaBePropTypes;
因此,直到您在 Wrapper
组件中将 children
定义为 ReactElement<FullProps<SomeObject>>
,fn
是可选的。关于 Component
,我认为您可以将 props 强制转换为 FullProps
:
const { fn, name } = props as FullProps<T>;
但如果出于某种原因您需要更严格的代码,可以通过以下方式缩小 props
类型:
function isFullProps<T extends SomeObject>(props: PropTypes<T>): props is FullProps<T> {
return !!(props as FullProps<T>).fn;
}
function Component<T extends SomeObject>(props: PropTypes<T>) {
if (!isFullProps(props)) return <></>;
const { fn, name } = props;
const result = fn(name);
return <div>Hello, {result.someKey}</div>;
}
尽管在您的情况下,这个条件似乎始终为 false。
这是如何只处理 TypeScript 的方法。
顺便说一下:也许您可以将 WannaBePropTypes
对象的数组传递给 Wrapper
,而不是将 children 传递给它?如果 <Component name="Alice" />
不应该自己执行任何操作,这听起来更好。
英文:
If I inderstand your problem correctly, you want to call Component
as <Component name="Alice" />
and there should be some internal logic for two cases: when fn
was passed and when not. If so, you can create unnecessary type (instead of unnecessary wrapper) which will be one of WannaBePropTypes
or full props. This is like some combination of your try#1 and try#2:
type FullProps<T extends SomeObject> = PropThatWillBeInjected<T> & WannaBePropTypes;
type PropTypes<T extends SomeObject> = FullProps<T> | WannaBePropTypes;
So fn
is optional until you define children as ReactElement<FullProps<SomeObject>>
in Wrapper
component.
As for the Component
, I think you can just cast props to FullProps
:
const { fn, name } = props as FullProps<T>;
But if you need more strict code for some reason, you can narrow props
type this way:
function isFullProps<T extends SomeObject>(props: PropTypes<T>): props is FullProps<T> {
return !!(props as FullProps<T>).fn;
}
function Component<T extends SomeObject>(props: PropTypes<T>) {
if (!isFullProps(props)) return <></>;
const { fn, name } = props;
const result = fn(name);
return <div>Hello, {result.someKey}</div>;
}
Though it seems this condition will always be false in your case.
This is how to tackle with Typescript only.
BTW: maybe you can just pass array of WannaBePropTypes
objects into Wrapper
instead of children? This sounds better if <Component name="Alice" />
should do nothing by itself.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论