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

TypeScript函数返回类型推断错误问题排查与修复

问题分析与修复方案

问题描述

编写了如下Zod代码,期望LegalAdress类型为仅包含street和可选office的对象,但实际得到的是residence和legal地址类型的联合类型:

import { z } from "zod"

export const { object, string, array, union, literal } = z

type AddressKind = "residence" | "legal"

export function address<TKind extends AddressKind>(kind: TKind) {
    const base = object({
        street: z.string().max(50),
    })

    switch (kind) {
        case "residence": {
            return base.merge(
                object({
                    flat: z.optional(z.string().max(10)),
                }),
            )
        }

        case "legal": {
            return base.merge(
                object({
                    office: z.optional(z.string().max(10)),
                }),
            )
        }

        default: {
            throw new Error("Unexpected kind")
        }
    }
}

export type Address<Kind extends AddressKind> = z.infer<ReturnType<typeof address<Kind>>>

export type LegalAdress = Address<"legal">
// 实际类型:
// type LegalAdress = {
//     street: string;
//     flat?: string | undefined;
// } | {
//     street: string;
//     office?: string | undefined;
// }

期望的LegalAdress类型:

{
    street: string;
    office?: string | undefined;
}

问题原因

TypeScript无法根据泛型参数TKind自动缩小address函数的返回类型。虽然函数内部通过switch case根据kind参数返回不同的Zod schema,但编译器无法将泛型类型与具体返回值类型建立关联,因此推断出的返回类型是两种schema的联合类型,最终导致z.infer得到的也是联合类型。

修复方案

以下两种方式均可解决问题:

方式一:使用函数重载

通过函数重载明确指定每种kind对应的返回类型,让TypeScript准确推断:

import { z } from "zod"

export const { object, string, array, union, literal } = z

type AddressKind = "residence" | "legal"

// 函数重载定义
export function address(kind: "residence"): z.ZodObject<{
    street: z.ZodString;
    flat: z.ZodOptional<z.ZodString>;
}>;
export function address(kind: "legal"): z.ZodObject<{
    street: z.ZodString;
    office: z.ZodOptional<z.ZodString>;
}>;
// 实现函数
export function address(kind: AddressKind) {
    const base = object({
        street: z.string().max(50),
    })

    switch (kind) {
        case "residence": {
            return base.merge(
                object({
                    flat: z.optional(z.string().max(10)),
                }),
            )
        }

        case "legal": {
            return base.merge(
                object({
                    office: z.optional(z.string().max(10)),
                }),
            )
        }

        default: {
            throw new Error("Unexpected kind")
        }
    }
}

export type Address<Kind extends AddressKind> = z.infer<ReturnType<typeof address<Kind>>>

export type LegalAdress = Address<"legal">
// 现在类型正确:{ street: string; office?: string | undefined; }

方式二:使用条件类型约束返回类型

通过条件类型映射TKind到对应的schema类型,给函数添加明确的返回类型:

import { z } from "zod"

export const { object, string, array, union, literal } = z

type AddressKind = "residence" | "legal"

// 定义每种类型对应的schema类型
type AddressSchema<T extends AddressKind> = 
    T extends "residence" ? z.ZodObject<{
        street: z.ZodString;
        flat: z.ZodOptional<z.ZodString>;
    }> :
    T extends "legal" ? z.ZodObject<{
        street: z.ZodString;
        office: z.ZodOptional<z.ZodString>;
    }> : never;

export function address<TKind extends AddressKind>(kind: TKind): AddressSchema<TKind> {
    const base = object({
        street: z.string().max(50),
    })

    switch (kind) {
        case "residence": {
            return base.merge(
                object({
                    flat: z.optional(z.string().max(10)),
                }),
            ) as AddressSchema<TKind>;
        }

        case "legal": {
            return base.merge(
                object({
                    office: z.optional(z.string().max(10)),
                }),
            ) as AddressSchema<TKind>;
        }

        default: {
            throw new Error("Unexpected kind")
        }
    }
}

export type Address<Kind extends AddressKind> = z.infer<AddressSchema<Kind>>

export type LegalAdress = Address<"legal">
// 现在类型正确:{ street: string; office?: string | undefined; }

内容的提问来源于stack exchange,提问作者Pavel Perevezencev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 10:02:35