You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 15:49:52