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类型,导致报错。
修复步骤
- 移除
PropsWithChildren包裹:直接使用IProps<T>作为组件的参数类型,因为你已经在接口中明确定义了children的类型。 - 优化
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
相关产品推荐
相关产品推荐

