【问题标题】:TypeScript: force a variable recognized as one type without castingTypeScript:强制将变量识别为一种类型而不进行强制转换
【发布时间】:2019-03-25 00:46:38
【问题描述】:

我正在为 TypeScript 的类型检查而苦苦挣扎。例如以下代码:

export function deepClone<T>(obj: T): T { // make sure that deepClone generates the same type as obj
  if (obj == null || typeof obj !== 'object') {
    return obj;
  }

  switch (Object.prototype.toString.call(obj)) {
    case '[object Array]': {
      const result = new Array(obj.length);
      for (let i=0; i<result.length; ++i) {
        result[i] = deepClone(obj[i]);
      }
      return result as any as T;
    }

    // Object.prototype.toString.call(new XxxError) returns '[object Error]'
    case '[object Error]': {
      const result = new obj.constructor(obj.message);
      result.stack = obj.stack; // hack...
      return result;
    }

    case '[object Date]':
    case '[object RegExp]':
    case '[object Int8Array]':
    case '[object Uint8Array]':
    case '[object Uint8ClampedArray]':
    case '[object Int16Array]':
    case '[object Uint16Array]':
    case '[object Int32Array]':
    case '[object Uint32Array]':
    case '[object Float32Array]':
    case '[object Float64Array]':
    case '[object Map]':
    case '[object Set]':
      return new obj.constructor(obj);

    case '[object Object]': {
      const keys = Object.keys(obj);
      const result: any = {};
      for (let i=0; i<keys.length; ++i) {
        const key = keys[i];
        result[key] = deepClone(obj[key]);
      }
      return result;
    }

    default: {
      throw new Error("Unable to copy obj! Its type isn't supported.");
    }
  }
}

我在const result = new Array(obj.length) 上遇到错误。我知道 obj 的类型是 any[] 但 ts 编译器无法识别它。我必须写丑陋的const tmp = obj as any as any[],但这会导致额外的无用代码生成,或者我必须在使用obj的每一行中写obj as any as whatever

function deepClone&lt;T extends any&gt;(obj: T): T 可以,但它会禁用大多数类型检查。

另一种情况:

const el = document.getElementById('sth');
switch (el.tagName) {
  case 'INPUT': // Now I know el is a HTMLInputElement element
    el.value = '123'; // Error: HTMLElement doesn't contain property 'value'
    (el as HTMLInputElement).value = '123'; // works
    (el as HTMLInputElement).valueAsNumber = 123; // again
    (el as HTMLInputElement).valueAsDate = xxx; // unacceptable

【问题讨论】:

  • stackoverflow.com/questions/40081332/… 使用is 关键字而不是与字符串化构造函数进行比较,这太可怕了
  • 你的意思是instanceof吗?
  • 如何将is 与switch 语句一起使用?或者至少不用写另一个新函数?

标签: javascript typescript


【解决方案1】:

如果您想更接近无断言代码,则需要使用类型保护。有几种可能的类型保护,但打开 toString 的结果不是其中之一。使用 instanceof 和自定义类型保护的版本可能是:

export function deepClone<T
    extends
    number | string | boolean |
    Array<any> |
    Error |
    Date | RegExp |
    Int8Array | Uint8Array | Uint8ClampedArray |
    Int16Array | Uint16Array |
    Int32Array | Uint32Array |
    Float32Array | Float64Array | Map<any, any> | Set<any>>(obj: T): T { // make sure that deepClone generates the same type as obj
    if (obj == null || typeof obj !== 'object') {
        return obj;
    }

    if (obj instanceof Array) {
        const result = new Array(obj.length);
        for (let i = 0; i < result.length; ++i) {
            result[i] = deepClone(obj[i]);
        }
        return result as any as T;
    }
    else if (obj instanceof Error) {
        const constructor = obj.constructor as new (p: any) => T & Error;
        const result = new constructor(obj.message);
        result.stack = obj.stack;
        return result;
    }
    else if (obj instanceof Date || obj instanceof RegExp ||
        obj instanceof Int8Array || obj instanceof Uint8Array || obj instanceof Uint8ClampedArray ||
        obj instanceof Int16Array || obj instanceof Uint16Array ||
        obj instanceof Int32Array || obj instanceof Uint32Array ||
        obj instanceof Float32Array || obj instanceof Float64Array || obj instanceof Map || obj instanceof Set) {

        const constructor = obj.constructor as new (p: T) => T;
        return new constructor(obj);
    }
    else if (isObject(obj)) {
        const keys = Object.keys(obj);
        const result: any = {};
        for (let i = 0; i < keys.length; ++i) {
            const key = keys[i];
            result[key] = deepClone(obj[key]);
        }
        return result;
    } else {
        throw new Error("Unable to copy obj! Its type isn't supported.");

    }

    function isObject(obj: object | T): obj is { [k: string]: any } {
        return typeof obj === 'object'
    }
}

【讨论】:

  • 难道Object#toString.call 不带开关(可能还有slice(8, -1))导致代码比instanceofs 更简洁、更少?
  • 快速基准测试:instanceof 在我的机器(Chrome 70)上比 Object#toString.call 慢 15% jsperf.com/object-prototype-tostring-call-vs-instanceof/1
  • @CarterLi 并没有说它更快,只是 Typescript 不理解你的版本是类型保护。
  • 我决定写一个d.ts文件,保留原来的js版本。感谢您的 cmets。
猜你喜欢
  • 2015-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-16
  • 1970-01-01
  • 2014-02-16
  • 2017-05-10
  • 2014-02-04
相关资源
最近更新 更多