gpt4 book ai didi

reactjs - React FunctionComponent 的 TypeScript 类型,它只返回一个 IntrinsicElement

转载 作者:行者123 更新时间:2023-12-04 13:26:02 25 4
gpt4 key购买 nike

我想创建一个 TypeScript 类型来保证 React FunctionComponent最终只渲染一个 HTML 元素。也就是说,它不能是 string , number , ReactFragment , ReactPortal (除非,我想,该门户只返回一个 HTML 元素),null ,或 boolean .
我的理解是所有 HTML 元素的类型是

type IntrinsicElement = JSX.IntrinsicElements<keyof JSX.IntrinsicElements>;

const anIntrinisicElement: IntrinsicElement = <div />;

// @ts-expect-error
const notAnIntrinsicElement: IntrinsicElement = 'foo';
这确实可以编译。
所以,我希望以下内容适用于 FunctionComponent正好返回一个 IntrinsicElement :
type IntrinsicFC<P = unknown> = FC<P> & ((props: P) => IntrinsicElement);

const SuccessFC: IntrinsicFC = () => <div>Goodbye</div>;
const FCWithProps: IntrinsicFC<{ a: number }> = (props: { a: number }) =>
<div>{props.a}</div>;

// @ts-expect-error
const FragmentFC: IntrinsicFC = () =>
<>
<div>Hello</div>
foo
</>;

// @ts-expect-error
const NullFC = () => null;

// @ts-expect-error
const TextFC = () => 'foo';
奇怪的是,这不能编译。 // @ts-expect-error 都没有组件实际上有问题。
如果这确实有效,那么我怀疑我的最终类型将是:
type IntrinsicFC<P = unknown> = FC<P> & {
(props: P): IntrinsicElement | IntrinsicFC;
};

const NestedSuccessFC: IntrinsicFC = () => <SuccessFC />;

// @ts-expect-error
const NestedFragmentFC: IntrinsicFC = () => <FragmentFC />; // erroneously valid
所以,我的问题是,为什么 IntrinsicElement拒绝 string , number , 和 null ,但是 IntrinsicFC不是,我该如何解决?
更新 : 我发现 ReactFragment s,由于某种原因,成功编译为 IntrinsicElement s。这可能是因为 ReactFragment定义为 {} | ReactNodeList , 和 {}只是“任何不无效的东西”。另一方面, ReactFragment不延长 IntrinsicElement ,所以我不确定为什么可以分配它。这可能是也可能不是问题的根源。
type IsIntrinsicElementAReactFragment =
IntrinsicElement extends ReactFragment ? true : false; // true

type IsReactFragmentAnIntrinsicElement =
ReactFragment extends IntrinsicElement ? true : false; // false

// erroneously valid
// @ts-expect-error
const fragmentIsNotAnIntrinsicElement: IntrinsicElement = <>foo</>;

// correctly invalid
// @ts-expect-error
const notAnIntrinsicElement3: IntrinsicElement = <TextFC />;
TS Playground

最佳答案

这是一个非常有趣的问题。 前段时间,我花了很多时间研究 react 内置类型。这是我发现/理解的:
如果您使用 jsx语法,您无法推断出某些特定类型。
查看 FC 的类型定义

    interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}

type VFC<P = {}> = VoidFunctionComponent<P>;

interface VoidFunctionComponent<P = {}> {
(props: P, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
您可能已经注意到,每个 FUnctionCOMponent 返回 ReactElement<any,any> .
就是这样 Reactlement好像:
    interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
type: T;
props: P;
key: Key | null;
}

我们有兴趣 T - 输入泛型参数。它延伸 JSXElementConstructor
    type JSXElementConstructor<P> =
| ((props: P) => ReactElement | null)
| (new (props: P) => Component<P, any>);
JSXElementConstructor只是一个递归。这里没有任何帮助。
React Native 语法是一种更有趣的方式。
考虑下一个例子:
const foo = React.createElement("a"); // ok

这就是魔法发生的地方。看看 createElement打字:
    // DOM Elements
// TODO: generalize this to everything in `keyof ReactHTML`, not just "input"
function createElement(
type: "input",
props?: InputHTMLAttributes<HTMLInputElement> & ClassAttributes<HTMLInputElement> | null,
...children: ReactNode[]): DetailedReactHTMLElement<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
function createElement<P extends HTMLAttributes<T>, T extends HTMLElement>(
type: keyof ReactHTML,
props?: ClassAttributes<T> & P | null,
...children: ReactNode[]): DetailedReactHTMLElement<P, T>;
function createElement<P extends SVGAttributes<T>, T extends SVGElement>(
type: keyof ReactSVG,
props?: ClassAttributes<T> & P | null,
...children: ReactNode[]): ReactSVGElement;
function createElement<P extends DOMAttributes<T>, T extends Element>(
type: string,
props?: ClassAttributes<T> & P | null,
...children: ReactNode[]): DOMElement<P, T>;

// Custom components

function createElement<P extends {}>(
type: FunctionComponent<P>,
props?: Attributes & P | null,
...children: ReactNode[]): FunctionComponentElement<P>;
function createElement<P extends {}>(
type: ClassType<P, ClassicComponent<P, ComponentState>, ClassicComponentClass<P>>,
props?: ClassAttributes<ClassicComponent<P, ComponentState>> & P | null,
...children: ReactNode[]): CElement<P, ClassicComponent<P, ComponentState>>;
function createElement<P extends {}, T extends Component<P, ComponentState>, C extends ComponentClass<P>>(
type: ClassType<P, T, C>,
props?: ClassAttributes<T> & P | null,
...children: ReactNode[]): CElement<P, T>;
function createElement<P extends {}>(
type: FunctionComponent<P> | ComponentClass<P> | string,
props?: Attributes & P | null,
...children: ReactNode[]): ReactElement<P>;
这个函数什么都知道。你要吗 IntrinsicElements ?没问题!
const createElement = <T extends keyof JSX.IntrinsicElements>(elem: T) =>
React.createElement(elem);

const test = createElement("a"); // ok
const test_ = createElement(null); // error
类型,您感兴趣的 int 是: DetailedReactHTMLElement<HTMLAttributes<HTMLElement>, HTMLElement>您可以在 createElement 中找到它重载。
随意尝试。
前段时间我写了两篇关于这个话题的文章。你可以在我的博客中找到它们:
Typing react children
Typing react return type
不严格相关,但仍然可能对您感兴趣: Typing react props
我不确定我是否已经回答了您的问题,但希望我的回答对您有所帮助
更新
您也可以覆盖 FUnctionComponent接口(interface),它部分帮助但仍然允许 React.Fragment:
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>

}

// ok
const Foo: FunctionComponent<{ label: string }> = (props) => {
return <div></div>
}

// error
const Bar: FunctionComponent<{ label: string }> = (props) => {
return 42
}

// error
const Baz: FunctionComponent<{ label: string }> = (props) => {
return null
}

// error
const Bat: FunctionComponent<{ label: string }> = (props) => {
return 'str'
}

const x = React.Fragment
// no error
const Fragment: FunctionComponent<{ label: string }> = (props) => {
return <></>
}

关于reactjs - React FunctionComponent 的 TypeScript 类型,它只返回一个 IntrinsicElement,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68398560/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com