自定义JSX工厂类型配置错误,children属性类型校验失败
自定义JSX行为树的Children类型错误修复方案
问题场景
我尝试用JSX语法构建层级化的行为树,示例代码如下:
const EnsureTargetNearby = ( <Fallback comment="Ensure mob nearby"> <IsMonsterNearby mobTypes={mobTarget} /> <MoveTo destinationOrKey={mobTarget} /> </Fallback> ); const EnsureAlive = ( <Fallback comment="Ensure char alive"> <IsAlive /> <ThrottleDecorator delay={1000}> <Respawn /> </ThrottleDecorator> </Fallback> );
但遇到Children属性的类型错误:
Property 'children' is missing in type '{ comment: string; }' but required in type 'FallbackProps'.
强制转译后代码可正常执行,但TypeScript类型检查始终报错,自定义的JSX命名空间似乎被忽略。
最小复现代码
/* * @jsx jsx */ // Base node for the Behavior Tree export abstract class BTNode { abstract tick(): boolean; } export type JSXNode = BTNode; export interface JSXChildren { children?: JSXNode | JSXNode[]; } export type FunctionComponent = (props: Record<string, unknown>) => BTNode; export type ClassComponent = new (props: Record<string, unknown>) => BTNode; declare namespace JSX { export interface IntrinsicElements { } // Declare the shape of JSX rendering result // This is required so the return types of components can be inferred export type Element = BTNode; // Declare the shape of JSX components export type ElementClass = BTNode; } // eslint-disable-next-line @typescript-eslint/ban-types const isESClass = (fn: Function): fn is new (...args: any[]) => any => typeof fn === "function" && Object.getOwnPropertyDescriptor(fn, "prototype")?.writable === false; export function jsx( tag: FunctionComponent | ClassComponent, props: Record<string, unknown>, ...children: BTNode[] ): JSX.Element { const fullProps = { ...props, children }; if (tag instanceof BTNode) { return tag; } if (isESClass(tag)) { const ClassTag = tag as new (props_: Record<string, unknown>) => JSX.Element; return new ClassTag(fullProps); } return tag(fullProps); } export interface FallbackProps { comment: string; children: BTNode[]; } export class Fallback extends BTNode { protected children: BTNode[] = []; comment: string; constructor({ comment, children }: FallbackProps) { super(); this.children = children; this.comment = comment; } override tick(): boolean { for (const child of this.children) { const status = child.tick(); if (status !== false) { return status; } } return false; } } export class AlwaysTrue extends BTNode { override tick(): boolean { return true; } } // The error is on the opening Fallback tag below const behaviorTree = ( <Fallback comment="My description"> <AlwaysTrue /> <AlwaysTrue /> </Fallback> )
修复方案
问题出在TypeScript对自定义JSX类组件的类型推断逻辑上,当前的JSX.ElementClass定义未告知TypeScript如何自动注入children属性。需通过以下步骤修复:
1. 修改JSX命名空间,添加组件属性解析规则
新增ComponentAttributes接口,告诉TypeScript自动处理JSX子元素到children属性的映射:
declare namespace JSX { export interface IntrinsicElements { } export type Element = BTNode; export type ElementClass = BTNode; // 定义组件属性的解析规则,自动识别children export interface ComponentAttributes<T extends ElementClass> { children?: JSXNode | JSXNode[]; [key: string]: unknown; } }
2. 优化Props类型定义
使用PropsWithChildren(可从React导入或自定义)统一处理props与children的组合,确保类型推断一致:
// 自定义PropsWithChildren,避免依赖React type PropsWithChildren<P> = P & { children?: JSXNode | JSXNode[] }; // 更新组件类型定义 export type FunctionComponent = (props: PropsWithChildren<Record<string, unknown>>) => BTNode; export type ClassComponent = new (props: PropsWithChildren<Record<string, unknown>>) => BTNode;
3. 调整jsx函数的children处理逻辑
确保JSX子元素能正确合并到props中:
export function jsx( tag: FunctionComponent | ClassComponent, props: PropsWithChildren<Record<string, unknown>>, ...children: BTNode[] ): JSX.Element { // 优先使用函数参数传递的children,否则用props中的children const fullProps = { ...props, children: children.length > 0 ? children : (props.children ?? []) }; if (tag instanceof BTNode) { return tag; } if (isESClass(tag)) { const ClassTag = tag as new (props_: PropsWithChildren<Record<string, unknown>>) => JSX.Element; return new ClassTag(fullProps); } return tag(fullProps); }
完整修复后的代码
/* * @jsx jsx */ // Base node for the Behavior Tree export abstract class BTNode { abstract tick(): boolean; } export type JSXNode = BTNode; // 自定义PropsWithChildren类型 type PropsWithChildren<P> = P & { children?: JSXNode | JSXNode[] }; export type FunctionComponent = (props: PropsWithChildren<Record<string, unknown>>) => BTNode; export type ClassComponent = new (props: PropsWithChildren<Record<string, unknown>>) => BTNode; declare namespace JSX { export interface IntrinsicElements { } export type Element = BTNode; export type ElementClass = BTNode; // 新增组件属性解析规则 export interface ComponentAttributes<T extends ElementClass> { children?: JSXNode | JSXNode[]; [key: string]: unknown; } } // eslint-disable-next-line @typescript-eslint/ban-types const isESClass = (fn: Function): fn is new (...args: any[]) => any => typeof fn === "function" && Object.getOwnPropertyDescriptor(fn, "prototype")?.writable === false; export function jsx( tag: FunctionComponent | ClassComponent, props: PropsWithChildren<Record<string, unknown>>, ...children: BTNode[] ): JSX.Element { const fullProps = { ...props, children: children.length > 0 ? children : (props.children ?? []) }; if (tag instanceof BTNode) { return tag; } if (isESClass(tag)) { const ClassTag = tag as new (props_: PropsWithChildren<Record<string, unknown>>) => JSX.Element; return new ClassTag(fullProps); } return tag(fullProps); } export interface FallbackProps { comment: string; children: BTNode[]; } export class Fallback extends BTNode { protected children: BTNode[] = []; comment: string; constructor({ comment, children }: FallbackProps) { super(); this.children = children; this.comment = comment; } override tick(): boolean { for (const child of this.children) { const status = child.tick(); if (status !== false) { return status; } } return false; } } export class AlwaysTrue extends BTNode { override tick(): boolean { return true; } } // 类型检查通过,无错误 const behaviorTree = ( <Fallback comment="My description"> <AlwaysTrue /> <AlwaysTrue /> </Fallback> )
原理说明
TypeScript的自定义JSX体系需通过JSX.ComponentAttributes明确组件props的解析规则,编译器才能识别JSX子元素会自动转换为children属性传递给组件。之前的代码缺少该配置,导致TypeScript无法推断出JSX子元素对应FallbackProps中的children字段,从而抛出缺失属性的错误。
内容的提问来源于stack exchange,提问作者Telokis
相关产品推荐
相关产品推荐

