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

如何在TypeScript层级数据类型上应用开闭原则(不可修改自动生成类型)

函数式实现符合开闭原则的Problem类型验证方案

现有不可修改的TypeScript类型

type BaseProblem = {
    name?: string;
    description?: string;
}

type SimProblem = BaseProblem & {
    type: "sim";
    simNumber?: string;
}

type CarProblem = BaseProblem & {
    type: "car";
    model?: string;
    plate: string;
}

type AllKeys<T> = T extends unknown ? keyof T : never;
type Id<T> = T extends infer U ? {[K in keyof U]: U[K]} : never;
type _ExclusifyUnion<T, K extends PropertyKey> = T extends unknown
    ? Id<T & Partial<Record<Exclude<K, keyof T>, never>>>
    : never;

declare type ExclusifyUnion<T> = _ExclusifyUnion<T, AllKeys<T>>;

type Problem = ExclusifyUnion<CarProblem | SimProblem>;

问题现状

当前的isValidToBeSent函数通过switch判断type字段实现验证,每次新增Problem类型或修改某类型的验证规则时,都要修改这个函数,完全违反开闭原则(对扩展开放,对修改关闭)。

函数式解决方案

核心思路是用策略模式的函数式实现:把每种类型的验证逻辑拆成独立函数,用注册表维护类型与验证函数的映射,新增类型只需要添加新的验证函数并注册,无需修改原有核心代码。

1. 定义统一的验证函数类型

先给所有验证函数定好类型约束,确保类型安全:

// 验证函数统一接口:接收对应类型的Problem,返回布尔值表示是否合法
type ProblemValidator<T extends Problem> = (problem: T) => boolean;

2. 实现各类型的独立验证逻辑

把原来switch里的逻辑拆成单独的函数,各自负责对应类型的验证规则:

// SimProblem专属验证逻辑
const validateSimProblem: ProblemValidator<SimProblem> = (problem) => {
    // 示例规则:simNumber不能为空
    return !!problem.simNumber;
};

// CarProblem专属验证逻辑
const validateCarProblem: ProblemValidator<CarProblem> = (problem) => {
    // 示例规则:plate必填,若填写model则不能为空
    return !!problem.plate && (problem.model ? !!problem.model : true);
};

3. 维护验证函数注册表

用对象映射type字段到对应验证函数,相当于一个可扩展的策略库:

// 注册表:key为Problem的type值,value为对应类型的验证函数
const problemValidators: Record<Problem["type"], ProblemValidator<any>> = {
    sim: validateSimProblem,
    car: validateCarProblem,
};

4. 实现通用的验证入口函数

这个函数是固定的,后续扩展类型无需修改:

const isValidToBeSent = (problem: Problem): boolean => {
    // 根据type从注册表获取对应验证函数
    const validator = problemValidators[problem.type];
    // 兜底逻辑:找不到对应验证函数时默认返回不合法(可根据业务调整)
    if (!validator) return false;
    return validator(problem);
};

5. 扩展新类型的方式

如果新增PhoneProblem类型,只需要两步操作:

// 假设新增自动生成的PhoneProblem类型
type PhoneProblem = BaseProblem & {
    type: "phone";
    phoneNumber: string;
};
// 更新Problem类型(同步自动生成的定义)
type Problem = ExclusifyUnion<CarProblem | SimProblem | PhoneProblem>;

// 1. 实现新类型的验证函数
const validatePhoneProblem: ProblemValidator<PhoneProblem> = (problem) => {
    // 示例规则:手机号格式校验
    return /^1[3-9]\d{9}$/.test(problem.phoneNumber);
};

// 2. 将验证函数注册到注册表
problemValidators.phone = validatePhoneProblem;

6. React组件中的使用示例

直接调用通用的isValidToBeSent即可,无需关心具体类型:

import { useState } from 'react';

const ProblemForm = () => {
    const [problem, setProblem] = useState<Problem>({
        type: "car",
        plate: ""
    });

    const handleSubmit = () => {
        if (isValidToBeSent(problem)) {
            console.log("提交合法问题:", problem);
            // 执行提交接口逻辑
        } else {
            alert("问题信息不合法,请检查");
        }
    };

    // 省略表单输入、类型切换等逻辑...

    return (
        <div className="problem-form">
            {/* 表单输入组件 */}
            <button onClick={handleSubmit}>提交问题</button>
        </div>
    );
};

export default ProblemForm;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 22:31:11