javascript - React 组件的自定义类型 - props 不匹配
我正在尝试创建一个函数,它接受两个参数:一个 React 函数组件及其 props 对象,并且有以下要求:
- 该函数应该能够只接受一个参数,当没有组件所需的 props 时
- 当组件的 props 缺失或与各自的类型不匹配时,Typescript 应该报错
`
import React, { PropsWithChildren, FC, FunctionComponent } from 'react';
function render<T> (
Component: FC<PropsWithChildren<T>>,
props = {} as PropsWithChildren<PropsWithChildren<T>>,
): JSX.Element {
return <Component { ...props }>{ props?.children }</Component>;
}
interface FooProps { name: string; }
const Foo = ({ name }: FooProps): JSX.Element => <p>{name}</p>;
const Bar = (): JSX.Element => <p>bar</p>;
// Incorrect
render(Foo, { name: 'asdd', a: '' }); // Passing. Expected to fail because prop 'a' is not expected
render(Foo); // Passing. Expected to fail because the component's prop 'name' is missing
render(Bar, { name: 'test' }); // Passing. Expected to fail, because Bar's props object is incorrect
// Correct
render(Foo, {}); // Failing. Expected to fail because prop 'name' is required
render(Foo, { name: 'asdd' }); // Passing. Expected to pass with correct props
render(Bar); // Passing. Expected to pass because Bar does not require any props
render(Bar, {}); // Passing. Expected to pass because Bar does not require any props
`
使用当前版本的代码,我能够满足大部分要求,除了以下几点:
- 当需要时,Typescript 应该要求 props 对象
- Typescript 不应该允许传递组件不需要的 props
不确定我哪里做错了。任何建议都将不胜感激。
答案#1
我认为你对 PropsWithChildren 有一些误解。FC 已经在幕后使用了 PropsWithChildren,所以 FC<PropsWithChildren<T>> 是多余的。PropsWithChildren<PropsWithChildren<T>> 显然也是多余的。我认为你在那里使用两次,是为了修复因为将它添加到 FC 而导致的错误。
children 可以包含在展开运算符中。<Component { ...props }>{ props?.children }</Component> 与仅使用 <Component { ...props }/> 是一样的。
为了仅针对某些 T 的值使第二个参数可选,你需要将泛型与函数重载结合使用。
function render(Component: FC<{}>): JSX.Element
function render<T> (Component: FC<T>, props: PropsWithChildren<T>): JSX.Element
function render<T> (Component: FC<T>, props = {} as PropsWithChildren<T>): JSX.Element {
return <Component { ...props }/>;
}
这不会因为额外属性而报错。我可以告诉你原因,但我不确定如何修复。当你调用 render(Foo, { name: 'asdd', a: '' }) 时,它会被推断为 render<{ name: string; a: string; }>。对于该类型,Foo 作为渲染组件是完全有效的。它被假设会忽略额外的属性。
答案#2
你正在使用 TypeScript 泛型,但你怎么期望 TS 会发现当你传入 Foo 作为第一个参数时,应使用 FooProps。你的解决方案只有在调用时指定模板参数才会有效。例如。
render<FooProps>(Foo, { name: 'asdd', a: '' });如果你把 FC 参数作为模板类,可能会更好。例如。
import React, { PropsWithChildren, FC, FunctionComponent } from 'react';
function render<T>(
Component: (_props: T) => JSX.Element,
props: PropsWithChildren<T> = {} as PropsWithChildren<T>,
): JSX.Element {
return <Component {...props}>{props?.children}</Component>;
}
interface FooProps {
name: string;
}
interface BarProps {
}
const Foo = ({ name }: FooProps): JSX.Element => <p>{name}</p>;
const Bar = ({}: BarProps): JSX.Element => <p>bar</p>;
这将解决你大部分的问题。然而,将默认值定义为空对象将不会在缺少参数时导致错误,因为你必须在定义时将其转换为正确的类型,但在未提供时它会忽略验证(因为已转换的默认值将被使用,所以永远不会失败)。
