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

开发TypeScript库时是否需要手动校验参数?

TypeScript库是否需要添加运行时类型校验?

但凡对TypeScript的工作原理有粗浅了解,都能回答:‘需要。TypeScript仅会将代码转译为JavaScript,不会在运行时添加包括类型检查在内的额外行为。’

例如:

function example(parameter: string): void {
  console.log(parameter.charAt(1));
}

会被转译为:

"use strict";
function example(parameter) {
  console.log(parameter.charAt(1));
}

若JavaScript用户调用example(3),会抛出错误Uncaught TypeError: parameter.charAt is not a function。


为何纠结这个问题?

因为我计划花费数十小时优化我的库@yamato-daiwa/es-extensions,但更关键的是,这会让库体积数倍增长,绝非夸大其词。

无校验的addElementsToArray函数(1.6.x版本)

export default function addElementsToArray<ArrayElement>(
  namedParameters:
    {
      targetArray: Array<ArrayElement>;
      newElements: Array<ArrayElement>;
      mutably: boolean;
    } &
    (
      { toStart: true; } |
      { toEnd: true; } |
      { toPosition__numerationFrom0: number; } |
      { toPosition__numerationFrom1: number; }
    )
): Array<ArrayElement> {

  const workpiece: Array<ArrayElement> = namedParameters.mutably ?
      namedParameters.targetArray : [ ...namedParameters.targetArray ];

  if ("toStart" in namedParameters) {
    workpiece.unshift(...namedParameters.newElements);
    return workpiece;
  }


  if ("toEnd" in namedParameters) {
    workpiece.push(...namedParameters.newElements);
    return workpiece;
  }


  const positionOfFirstNewElement__numerationFrom0: number = "toPosition__numerationFrom0" in namedParameters ?
      namedParameters.toPosition__numerationFrom0 : namedParameters.toPosition__numerationFrom1 - 1;

  workpiece.splice(positionOfFirstNewElement__numerationFrom0, 0, ...namedParameters.newElements);

  return workpiece;
}

添加校验后的addElementsToArray函数

import Logger from "../Logging/Logger";
import InvalidParameterValueError from "../Errors/InvalidParameterValue/InvalidParameterValueError";
import IncompatiblePropertiesInObjectTypeParameterError from
    "../Errors/IncompatiblePropertiesInObjectTypeParameter/IncompatiblePropertiesInObjectTypeParameterError";
import stringifyAndFormatArbitraryValue from "../Strings/stringifyAndFormatArbitraryValue";
import isArbitraryObject from "../TypeGuards/Objects/isArbitraryObject";
import isNotUndefined from "../TypeGuards/Nullables/isNotUndefined";
import isNonNegativeInteger from "../TypeGuards/Numbers/isNonNegativeInteger";
import isNaturalNumber from "../TypeGuards/Numbers/isNaturalNumber";


export default function addElementsToArray<ArrayElement>(
  namedParameters:
      Readonly<
        (
          {
            mutably: true;
            targetArray: Array<ArrayElement>;
          } |
          {
            mutably: false;
            targetArray: ReadonlyArray<ArrayElement>;
          }
        ) &
        {
          newElements: ReadonlyArray<ArrayElement>;
          toStart?: true;
          toEnd?: true;
          toPosition__numerationFrom0?: number;
          toPosition__numerationFrom1?: number;
        }
      >
): Array<ArrayElement> {

  if (!isArbitraryObject(namedParameters)) {
    Logger.throwErrorAndLog({
      errorInstance: new InvalidParameterValueError({
        parameterNumber: 1,
        parameterName: "namedParameters",
        messageSpecificPart: "The first and only parameter of 'addElementsToArray' must be of the object type."
      }),
      title: InvalidParameterValueError.localization.defaultTitle,
      occurrenceLocation: "addElementsToArray(namedParameters)"
    });
  }


  if (!Array.isArray(namedParameters.targetArray)) {
    Logger.throwErrorAndLog({
      errorInstance: new InvalidParameterValueError({
        parameterName: "namedParameters",
        parameterNumber: 1,
        messageSpecificPart: "The 'targetArray' is not the valid array and has value:\n" +
            `${ stringifyAndFormatArbitraryValue(namedParameters.targetArray) }`
      }),
      title: InvalidParameterValueError.localization.defaultTitle,
      occurrenceLocation: "addElementsToArray(namedParameters)"
    });
  }


  const alternativelyRequestOptions: Array<true | number> = [
    namedParameters.toStart,
    namedParameters.toEnd,
    namedParameters.toPosition__numerationFrom0,
    namedParameters.toPosition__numerationFrom1
  ].filter(isNotUndefined);

  if (alternativelyRequestOptions.length > 1) {

    Logger.logError({
      errorType: IncompatiblePropertiesInObjectTypeParameterError.NAME,
      title: IncompatiblePropertiesInObjectTypeParameterError.localization.defaultTitle,
      description: "Exactly one of 'toStart', 'toEnd', 'toPosition__numerationFrom0', 'toPosition__numerationFrom1' " +
          "must be specified while actually multiple of them has been. No elements will be added to target array.",
      occurrenceLocation: "addElementsToArray(namedParameters)"
    });

    return namedParameters.targetArray;

  }


  const workpiece: Array<ArrayElement> = namedParameters.mutably ?
      namedParameters.targetArray : [ ...namedParameters.targetArray ];

  if (namedParameters.toStart === true) {
    workpiece.unshift(...namedParameters.newElements);
    return workpiece;
  }


  if (namedParameters.toEnd === true) {
    workpiece.push(...namedParameters.newElements);
    return workpiece;
  }


  let positionOfFirstNewElement__numerationFrom0: number;

  if (isNonNegativeInteger(namedParameters.toPosition__numerationFrom0)) {
    positionOfFirstNewElement__numerationFrom0 = namedParameters.toPosition__numerationFrom0;
  } else if (isNaturalNumber(namedParameters.toPosition__numerationFrom1)) {
    positionOfFirstNewElement__numerationFrom0 = namedParameters.toPosition__numerationFrom1 - 1;
  } else {

    Logger.logError({
      errorType: InvalidParameterValueError.NAME,
      title: InvalidParameterValueError.localization.defaultTitle,
      description: alternativelyRequestOptions.length === 0 ?
          "None one of 'toStart', 'toEnd', 'toPosition__numerationFrom0', 'toPosition__numerationFrom1' has been specified." :
          (
            "The specified value of 'toStart', 'toEnd', 'toPosition__numerationFrom0' or 'toPosition__numerationFrom1' has " +
            "invalid type, value or numbers set."
          ) +
          "No elements will be added to target array.",
      occurrenceLocation: "addElementsToArray(namedParameters)"
    });

    return namedParameters.targetArray;

  }


  workpiece.splice(positionOfFirstNewElement__numerationFrom0, 0, ...namedParameters.newElements);

  return workpiece;

}

此时校验代码约占一半行数,还引入了大量依赖。有时校验代码甚至会占函数行数的90%以上,库的分发包体积会数倍增长,这对每KB都至关重要的前端应用来说是个关键问题。

上述校验还不够细致!我们还可以:

  • 当互斥的命名参数(toStart、toEnd、toPosition__numerationFrom0、toPosition__numerationFrom1)传入多个时,检查具体是哪些参数并记录名称。
  • 将这些参数的类型检查与未传入任何参数的检查分开(目前这两项检查是同时进行的)。
  • 若上述参数的子类型不正确(例如toStart或toEnd为false,或toPosition__numerationFrom0/toPosition__numerationFrom1为负数或小数),单独记录该问题。

潜在的虚假问题

简而言之,非TypeScript用户可能会因参数类型无效而向我报告Bug,但如果用TypeScript编写相同代码,TypeScript会提前抛出错误。即便最终发现问题是因使用JavaScript而非TypeScript导致的参数类型无效,也会浪费大量调查和沟通时间。这类问题可能数量众多,但本质上只是TypeScript使用不当。


悬赏更新说明

这个问题已有答案,但参数校验可能导致库体积数倍增长的问题仍困扰着我。为彻底解决该问题,我需要资深/大师级Web开发者给出明确建议:要么“你做得对,继续坚持!即便因参数校验导致分发包体积数倍增长,对前端应用也无关紧要。”,要么“即便因参数校验,过大的工具库体积也不可取。你应跳过参数校验以保持库的轻量化。”


内容的提问来源于stack exchange,提问作者Takeshi Tokugawa YD

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 04:45:31