最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 「译」深入了解TypeScript系列-unknown类型

    正文概述 掘金(小筑佩乔)   2021-02-15   778

    TypeScript3.0介绍了新类型unknown这是any类型的类型安全(type-safe)的参照物。 unknow和any的主要区别在与:unknown比any更严格些:在对类型为unknown的值执行大多数操作之前,我们必须做某种形式的检查,而在对类型为any的值执行操作之前,我们不需要做任何检查。 这篇文章着重于unknow类型的实际方面,包括与任何类型的比较。要获得一个显示未知类型语义的全面代码示例,请查看Anders Hejlsberg最初的pull请求。这篇文章着重于未知类型的实际方面,包括与任何类型的比较。要获得一个显示未知类型语义的全面代码示例,请查看Anders Hejlsberg最初的pull请求。

    any类型

    让我们先看看any类型,以便更好地理解引入unknown类型背后的动机。 any类型自2012年第一次发布以来就一直在TypeScript中。它代表了所有可能的JavaScript值原语,对象,数组,函数,错误,符号,等等。 在TypeScript中,每种类型都可以赋值给any。这使得any成为类型系统的顶部类型(也称为通用超类型)。 下面是一些可以赋值给any类型变量的示例

    let value: any;
    
    value = true;             // OK
    value = 42;               // OK
    value = "Hello World";    // OK
    value = [];               // OK
    value = {};               // OK
    value = Math.random;      // OK
    value = null;             // OK
    value = undefined;        // OK
    value = new TypeError();  // OK
    value = Symbol("type");   // OK
    

    any类型本质上是类型系统的一个逃生舱口。作为开发人员,这给了我们很大的自由:TypeScript允许我们对any类型的值执行任何想要的操作,而不需要事先执行任何类型的检查。 在上面的例子中,value变量的类型为any。因此,TypeScript认为以下所有操作都是正确的类型

    let value: any;
    
    value.foo.bar;  // OK
    value.trim();   // OK
    value();        // OK
    new value();    // OK
    value[0][1];    // OK
    

    在很多情况下,这是太宽容了。使用any类型,很容易编写类型正确的代码,但在运行时存在问题。如果我们选择使用TypeScript,我们不会得到很多保护。如果有一个顶部类型默认是安全的呢?这就是unknown发挥作用的地方。

    unknown类型

    就像所有类型都可以赋值给any一样,所有类型都可以赋值给unknown。这使得TypeScript类型系统的另一个顶级类型(另一个是any)成为未知的。 这里是我们之前看到的赋值示例列表,这次使用的变量类型为unknown

    let value: unknown;
    value = true;             // OK
    value = 42;               // OK
    value = "Hello World";    // OK
    value = [];               // OK
    value = {};               // OK
    value = Math.random;      // OK
    value = null;             // OK
    value = undefined;        // OK
    value = new TypeError();  // OK
    value = Symbol("type");   // OK
    

    对value变量的所有赋值都被认为是类型正确的。 但是,当我们试图将unkonwn类型的值赋给其他类型的变量时,会发生什么呢

    let value: unknown;
    let value1: unknown = value;   // OK
    let value2: any = value;       // OK
    let value3: boolean = value;   // Error
    let value4: number = value;    // Error
    let value5: string = value;    // Error
    let value6: object = value;    // Error
    let value7: any[] = value;     // Error
    let value8: Function = value;  // Error
    

    unknown类型只能赋值给any类型和未知类型本身。直观地说,这是有意义的:只有能够保存任意类型值的容器才能保存未知类型的值;毕竟,我们不知道什么类型的价值存储在价值中。 现在让我们看看当我们尝试对unknown类型的值执行操作时会发生什么。这是我们之前看过的相同的操作。

    let value: unknown;
    value.foo.bar;  // Error
    value.trim();   // Error
    value();        // Error
    new value();    // Error
    value[0][1];    // Error
    

    由于value变量类型为unknow,这些操作都不再被认为是类型正确的。通过从“any”变为“unknown”,我们将默认设置从允许所有内容转变为(几乎)不允许任何内容。这是unknown类型的主要值命题:TypeScript不会让我们对未知类型的值执行任意操作。相反,我们必须首先执行某种类型检查,以缩小正在处理的值的类型。

    缩小unknown类型

    我们可以用不同的方法将unknown类型缩小为更特定的类型,包括typeof操作符、instanceof操作符和自定义类型保护函数。所有这些窄化技术都有助于TypeScript基于控制流的类型分析。 下面的例子说明了value如何在两个if语句分支中具有更特定的类型

    function stringifyForLogging(value: unknown): string {
      if (typeof value === "function") {
        // 在此分支中,“值”的类型为“function”,
        // 这样我们就可以访问函数的`name`属性
        const functionName = value.name || "(anonymous)";
        return `[function ${functionName}]`;
      }
      if (value instanceof Date) {
        // 在此分支中,“值”的类型为“Date”,
        // 所以我们可以调用`toISOString`方法
        return value.toISOString();
      }
      return String(value);
    }
    

    除了使用typeof或instanceof操作符之外,我们还可以使用自定义类型保护函数来缩小unknown类型

    /**
     * 一个自定义类型保护函数,用于确定是否
     * value 是否为只包含数字的数组。
     */
    function isNumberArray(value: unknown): value is number[] {
      return (
        Array.isArray(value) &&
        value.every(element => typeof element === "number")
      );
    }
    const unknownValue: unknown = [15, 23, 8, 4, 42, 16];
    if (isNumberArray(unknownValue)) {
      // 在这个分支中,' unknownValue '的类型是' number '
      // 因此,我们可以将这些数字作为参数分散到' Math.max '
      const max = Math.max(...unknownValue);
      console.log(max);
    }
    

    请注意,尽管unknownValue被声明为unknown类型,但它在if语句分支中是如何具有number[]类型的。请注意,尽管unknownValue被声明为未知类型,但它在if语句分支中是如何具有number[]类型的。

    使用unknown类型断言

    在上一节中,我们已经看到了如何使用typeof、instanceof和自定义类型保护函数来让TypeScript编译器相信一个值具有特定的类型。这是将未知类型的值缩小到更特定类型的安全且推荐的方法。 如果希望强制编译器相信类型未知的值是给定类型,可以使用类似这样的类型断言 const value: unknown = "Hello World"; const someString: string = value as string; const otherString = someString.toUpperCase(); // "HELLO WORLD" 请注意,TypeScript并没有执行任何特殊的检查来确保类型断言实际上是有效的。类型检查器假定您了解得更好,并相信您在类型断言中使用的任何类型都是正确的。如果您犯了错误并指定了不正确的类型,这很容易导致在运行时抛出错误

    const value: unknown = 42;
    const someString: string = value as string;
    const otherString = someString.toUpperCase();  // BOOM
    

    value变量保存一个数字,但是我们使用类型断言值作为字符串来假装它是一个字符串。小心使用类型断言!

    联合类型中的unknown类型

    现在让我们看看如何在联合类型中处理未知类型。在下一节中,我们还将研究交集类型。 在联合类型中,未知会吸收所有类型。这意味着,如果任何组成类型是unknown,则联合类型的计算结果为unknown。

    type UnionType1 = unknown | null;       // unknown
    type UnionType2 = unknown | undefined;  // unknown
    type UnionType3 = unknown | string;     // unknown
    type UnionType4 = unknown | number[];   // unknown
    

    这条规则有一个例外。如果至少有一个组成类型为any,则union类型的计算结果为any

    type UnionType5 = unknown | any;  // any
    

    那么,为什么unknown吸收了所有类型(除了any)呢?让我们考虑未知|字符串的例子。该类型表示所有可分配给未知类型的值加上可分配给string类型的值。正如我们之前所了解的,所有类型都可以赋值给unknown。这包括所有的字符串,因此,unknown | string表示与未知本身相同的值集。因此,编译器可以将union类型简化为unknown。

    交叉类型中的unknown类型

    在交叉类型中,每种类型都吸收unknown。这意味着将任何类型与unknown类型相交不会改变结果类型

    type IntersectionType1 = unknown & null;       // null
    type IntersectionType2 = unknown & undefined;  // undefined
    type IntersectionType3 = unknown & string;     // string
    type IntersectionType4 = unknown & number[];   // number[]
    type IntersectionType5 = unknown & any;        // any
    

    让我们来看看IntersectionType3: unknown & string类型代表了所有可分配给unknown和string的值。由于每种类型都可赋值给unknown,因此在交集类型中包含unknown不会改变结果。只剩下string类型。

    使用带有unknown类型值的操作符

    类型unknown的值不能用作大多数操作符的操作数。这是因为,如果不知道所处理的值的类型,大多数操作符都不太可能产生有意义的结果。 对于未知类型的值,唯一可以使用的操作符是四个相等和不相等操作符 • === • == • !== • != 如果希望对类型为未知的值使用任何其他操作符,则必须首先缩小类型范围(或使用类型断言强制编译器信任您)。

    示例:从本地存储中读取JSON

    下面是一个真实的例子,说明我们如何使用unknown类型。 让我们假设我们想要编写一个函数,从localStorage读取一个值并将其反序列化为JSON。如果项目不存在或不是有效的JSON,函数应该返回一个错误结果;否则,它应该反序列化并返回值。 因为我们不知道在对持久化的JSON字符串进行反序列化后会得到什么类型的值,所以我们将使用unknown作为反序列化后的值的类型。这意味着函数的调用者在对返回值执行操作(或使用类型断言)之前必须进行某种形式的检查。 下面是我们如何实现这个函数

    type Result =
      | { success: true, value: unknown }
      | { success: false, error: Error };
    function tryDeserializeLocalStorageItem(key: string): Result {
      const item = localStorage.getItem(key);
      if (item === null) {
        // The item does not exist, thus return an error result
        return {
          success: false,
          error: new Error(`Item with key "${key}" does not exist`)
        };
      }
      let value: unknown;
      try {
        value = JSON.parse(item);
      } catch (error) {
        // The item is not valid JSON, thus return an error result
        return {
          success: false,
          error
        };
      }
      // Everything's fine, thus return a success result
      return {
        success: true,
        value
      };
    }
    

    返回的类型结果是带标记的联合类型(也称为区别的联合类型)。在其他语言中,它也被称为“可能”、“选项”或“可选”。我们使用Result来清晰地模拟成功和不成功的操作结果。 tryDeserializeLocalStorageItem函数的调用者必须在尝试使用value或error属性之前检查success属性

    const result = tryDeserializeLocalStorageItem("dark_mode");
    if (result.success) {
      // We've narrowed the `success` property to `true`,
      // so we can access the `value` property
      const darkModeEnabled: unknown = result.value;
      if (typeof darkModeEnabled === "boolean") {
        // We've narrowed the `unknown` type to `boolean`,
        // so we can safely use `darkModeEnabled` as a boolean
        console.log("Dark mode enabled: " + darkModeEnabled);
      }
    } else {
      // We've narrowed the `success` property to `false`,
      // so we can access the `error` property
      console.error(result.error);
    }
    

    请注意,由于以下两个原因,tryDeserializeLocalStorageItem函数不能简单地返回null来表示反序列化失败

    1. null是一个有效的JSON值。因此,我们将无法区分反序列化的值是null,还是由于缺少项或语法错误而导致整个操作失败。
    2. 如果我们要从函数返回null,我们不能同时返回错误。因此,函数的调用者将不知道操作失败的原因。

    为了完整起见,这种方法的一种更复杂的替代方法是使用类型化解码器进行安全的JSON解析。解码器允许我们指定要反序列化的值的预期模式。如果持久化的JSON结果与该模式不匹配,则解码将以良好定义的方式失败。这样,我们的函数总是返回有效或失败的解码结果,我们可以完全消除unknown类型。


    起源地下载网 » 「译」深入了解TypeScript系列-unknown类型

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元