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

解决TypeScript报错:Property 'sma'不存在于TypedResponse<never>类型

问题场景

我有一个服务端数据loader,从数据模型中获取用户ID并校验其是否存在:

export const loader = async ({ request, params }: LoaderArgs) => {
    const REQUESTED_USER = await GetUserById(Number(params.id))

    if (!REQUESTED_USER) {
        /**
         * If the provided url params user ID does not exist, we will handle it here
         */
        return redirectWithError({
            request,
            message: `User ID: ${params.id} was not found; please enter a valid User ID`,
            logMessage: `User ID: ${params.id} was not found; please enter a valid User ID`,
            redirectTo: '/manager'
        })
    }
    // 后续数据处理逻辑
    if (GET_SELECTED_SERVICES) {
        sma = unwrap_sma[0]
         
         edr = unwrap_edr[0]

         cma = unwrap_cma[0]

         vms = unwrap_vms[0]
    }

    return typedjson({
        sma,
        vms,
        cma,
        edr,
        user: REQUESTED_USER
    })
}

REQUESTED_USER的类型为:

const REQUESTED_USER: {
    id: number;
    company_name: string | null;
    avatar: string | null;
    brand: string | null;
    email: string;
    name: string | null;
    favicon: string | null;
} | null

之后在客户端React组件中接收loader返回的数据:

export const ReadUser = ({ }) => {
    const LOADER = useTypedLoaderData<typeof loader>();
    const [CSRF] = useCSRF()

    
    return (
        <main className="flex w-full">
            <Form method="post" className="flex w-full">
                <input className="hidden" name={'csrf'} value={CSRF} type="text" readOnly />
                <Profile SMA={LOADER.sma} EDR={[]} VMS={[]} CMA={[]} />
            </Form>
        </main>
    )
}

此时Profile组件的属性抛出TypeScript错误:

Property 'sma' does not exist on type 'TypedResponse<never> | TypedJsonResponse<{ sma: TOptions[]; vms: TOptions[]; cma: TOptions[]; edr: TOptions[]; user: { id: number; company_name: string | null; ... 4 more ...; favicon: string | null; }; }>'.
  Property 'sma' does not exist on type 'TypedResponse<never>'.ts(2339)

LOADER的类型为:

const LOADER: TypedResponse<never> | TypedJsonResponse<{
    sma: TOptions[];
    vms: TOptions[];
    cma: TOptions[];
    edr: TOptions[];
    user: {
        id: number;
        company_name: string | null;
        ... 4 more ...;
        favicon: string | null;
    };
}>

我尝试将!REQUESTED_USER改为REQUESTED_USER === null,但没有效果,希望了解错误原因并解决。


原因解释

TypeScript报错的核心是:它无法在编译阶段确认,当组件执行到读取LOADER.sma的逻辑时,loader一定返回了带sma的JSON响应。

在loader代码中,当REQUESTED_USER不存在时,会返回redirectWithError(对应类型TypedResponse<never>);只有校验通过后,才会返回带sma等字段的TypedJsonResponse。但在客户端组件中,TypeScript仅会依据loader的返回类型集合进行判断,不会自动推断出“组件能渲染就说明校验已通过、不会返回TypedResponse<never>”这一运行时逻辑,因此认为LOADER仍有可能是不包含sma属性的TypedResponse<never>类型,进而抛出错误。


修复方案

方案1:使用类型断言明确类型

直接告知TypeScript当前LOADER就是带数据的TypedJsonResponse类型:

export const ReadUser = ({ }) => {
    const LOADER = useTypedLoaderData<typeof loader>() as TypedJsonResponse<{
        sma: TOptions[];
        vms: TOptions[];
        cma: TOptions[];
        edr: TOptions[];
        user: {
            id: number;
            company_name: string | null;
            avatar: string | null;
            brand: string | null;
            email: string;
            name: string | null;
            favicon: string | null;
        };
    }>;
    const [CSRF] = useCSRF()

    return (
        <main className="flex w-full">
            <Form method="post" className="flex w-full">
                <input className="hidden" name={'csrf'} value={CSRF} type="text" readOnly />
                <Profile SMA={LOADER.sma} EDR={[]} VMS={[]} CMA={[]} />
            </Form>
        </main>
    )
}

方案2:添加运行时校验(更安全)

先判断LOADER是否包含sma属性,再渲染组件,同时让TypeScript自动收窄类型:

export const ReadUser = ({ }) => {
    const LOADER = useTypedLoaderData<typeof loader>();
    const [CSRF] = useCSRF()

    // 若loader返回的是redirect响应,这里可以做兜底处理
    if (!('sma' in LOADER)) {
        return <div>加载失败,请重试</div>;
    }

    return (
        <main className="flex w-full">
            <Form method="post" className="flex w-full">
                <input className="hidden" name={'csrf'} value={CSRF} type="text" readOnly />
                <Profile SMA={LOADER.sma} EDR={[]} VMS={[]} CMA={[]} />
            </Form>
        </main>
    )
}

方案3:优化loader的类型推断(推荐)

改用throw方式抛出redirect响应(这是Remix等框架的标准做法),这样TypeScript会识别出抛出异常后后续代码不会执行,从而自动收窄loader的返回类型:

export const loader = async ({ request, params }: LoaderArgs) => {
    const REQUESTED_USER = await GetUserById(Number(params.id))

    if (!REQUESTED_USER) {
        // 改用throw方式抛出redirect,而非返回
        throw redirectWithError({
            request,
            message: `User ID: ${params.id} was not found; please enter a valid User ID`,
            logMessage: `User ID: ${params.id} was not found; please enter a valid User ID`,
            redirectTo: '/manager'
        })
    }

    // 初始化变量避免类型报错
    let sma: TOptions[] = [];
    let edr: TOptions[] = [];
    let cma: TOptions[] = [];
    let vms: TOptions[] = [];
    if (GET_SELECTED_SERVICES) {
        sma = unwrap_sma[0] || [];
        edr = unwrap_edr[0] || [];
        cma = unwrap_cma[0] || [];
        vms = unwrap_vms[0] || [];
    }

    return typedjson({
        sma,
        vms,
        cma,
        edr,
        user: REQUESTED_USER
    })
}

此时客户端的useTypedLoaderData<typeof loader>会自动推断出返回类型仅为TypedJsonResponse,不再包含TypedResponse<never>,错误自然消失。


内容的提问来源于stack exchange,提问作者T. Duke

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 23:20:38