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

React+TypeScript类型错误求助:Loadable组件类型不兼容排查

类型错误排查与解决:Loadable组件与UserDetailsPage组件类型不兼容

错误提示

Type '(user: User) => Element' is not assignable to type '((props: User) => any) & ReactNode'

问题场景

在使用Loadable组件包裹UserDetails组件时触发上述类型错误,本人TypeScript使用经验有限,以下是两个组件的实现代码,请求协助定位并修复问题。


Loadable组件代码

import { CircularProgress } from "@mui/material"
import { makeStyles, createStyles } from '@mui/styles';
import React, { PropsWithChildren } from "react"
import NoEntityFound from "../NoEntityFound"

const useStyles = makeStyles(
    () =>
        createStyles({
            loadingWrapper: {
                width: "100%",
                textAlign: "center",
                marginTop: 16,
            },
        }),
    { name: "Loadable" },
)

interface IProps<T> {
    error: any
    data: T | undefined
    errorMessage?: string | React.ReactNode
    renderOnError?: (node: React.ReactNode) => React.ReactNode
    children: (props: T) => any
}

const Loadable = <T,>({
    children,
    error,
    data,
    errorMessage,
    renderOnError,
}: PropsWithChildren<IProps<T>>) => {
    const defaultErrorMessage = "Entity not found!"

    const errorElement = (
        <NoEntityFound>{errorMessage || defaultErrorMessage}</NoEntityFound>
    )
    const classes = useStyles()

    if (error) {
        if (renderOnError) {
            return renderOnError(errorElement)
        }
        return errorElement
    } else if (!error && !data) {
        return (
            <div className={classes.loadingWrapper}>
                <CircularProgress />
            </div>
        )
    }

    if (typeof children !== "function") {
        console.warn("children prop must be a function!")
        return null
    }

    if (data) {
        return children(data)
    }

    return null
}

export default Loadable

UserDetailsPage组件代码

import { Theme } from "@mui/material"
import { makeStyles, createStyles } from '@mui/styles';
import BreadcrumbBox from "components/Breadcrumb/BreadcrumbBox"
import Loadable from "components/Loadable"
import UserDetails from "components/Users/UserDetails"
import RoutingContext from "contexts/RoutingContext"
import routes from "pages/routes"
import React, { useContext, useEffect, useState } from "react"
import { useParams } from "react-router-dom"
import { getUser } from "services/Request/mpayApi"
import useSWR from "swr"
import { PathParams } from "types/routes"

import PageLayout from "../../layouts/Default"

const useStyles = makeStyles(
    (theme: Theme) =>
        createStyles({
            menuWrapper: {
                display: "flex",
                flexDirection: "column",
                height: "100%",
            },
            menuContent: {
                flexGrow: 1,
            },
        }),
    {
        name: "UserDetailsPage",
    },
)

const UserDetailsPage: React.FunctionComponent = () => {
    const [editMode, setEditMode] = useState<boolean>(false)

    const { userId = "" } = useParams<PathParams>()
    const fetcher = () => getUser(userId).then((res) => res.data)
    const { data, error, mutate } = useSWR(userId, fetcher)

    const { setParentRoute } = useContext(RoutingContext)
    useEffect(() => {
        setParentRoute(routes.users.path)
    }, [setParentRoute])

    useEffect(() => {
        // Revalidate on ticket change
        mutate(undefined, true)
    }, [editMode, mutate])

    const classes = useStyles()

    return (
        <PageLayout>
            <BreadcrumbBox>
                <Loadable error={error} data={data}>
                    {(user) => (
                        <UserDetails
                            editMode={editMode}
                            setEditMode={setEditMode}
                            user={user}
                        />
                    )}
                </Loadable>
            </BreadcrumbBox>
        </PageLayout>
    )
}

export default UserDetailsPage

问题根源与修复方案

问题原因

Loadable组件的Props使用了PropsWithChildren<IProps<T>>包裹,而PropsWithChildren会自动添加一个children?: ReactNode的类型定义,和你在IProps<T>中声明的children: (props: T) => any产生类型冲突——TypeScript认为children需要同时满足函数类型和ReactNode类型,导致报错。

修复步骤

  1. 移除PropsWithChildren包裹:直接使用IProps<T>作为组件的参数类型,因为你已经在接口中明确定义了children的类型。
  2. 优化children返回值类型:将(props: T) => any改为(props: T) => React.ReactNode,让类型更严谨,避免any带来的类型安全问题。

修改后的Loadable组件代码

import { CircularProgress } from "@mui/material"
import { makeStyles, createStyles } from '@mui/styles';
import React from "react"
import NoEntityFound from "../NoEntityFound"

const useStyles = makeStyles(
    () =>
        createStyles({
            loadingWrapper: {
                width: "100%",
                textAlign: "center",
                marginTop: 16,
            },
        }),
    { name: "Loadable" },
)

interface IProps<T> {
    error: any
    data: T | undefined
    errorMessage?: string | React.ReactNode
    renderOnError?: (node: React.ReactNode) => React.ReactNode
    children: (props: T) => React.ReactNode // 替换any为ReactNode
}

const Loadable = <T,>({
    children,
    error,
    data,
    errorMessage,
    renderOnError,
}: IProps<T>) => { // 移除PropsWithChildren
    const defaultErrorMessage = "Entity not found!"

    const errorElement = (
        <NoEntityFound>{errorMessage || defaultErrorMessage}</NoEntityFound>
    )
    const classes = useStyles()

    if (error) {
        if (renderOnError) {
            return renderOnError(errorElement)
        }
        return errorElement
    } else if (!error && !data) {
        return (
            <div className={classes.loadingWrapper}>
                <CircularProgress />
            </div>
        )
    }

    if (typeof children !== "function") {
        console.warn("children prop must be a function!")
        return null
    }

    if (data) {
        return children(data)
    }

    return null
}

export default Loadable

额外优化建议

  • 给error字段定义更具体的类型(比如Error | undefined),替换any以提升类型安全性。
  • 确保getUser函数返回的data类型是明确的User类型,这样TypeScript可以自动推导Loadable的泛型参数T,无需手动指定。

内容的提问来源于stack exchange,提问作者Rafay Hassan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 07:03:17