- 原文地址:TypeScript 3.0: The unknown Type
- 原文作者:Marius Schulz
- 译文出自:掘金翻译打算
- 本文永恒链接:https://github.com/xitu/gold-miner/blob/master/TODO1/typescript-3-0-the-unknown-type.md
- 译者:shixi-li
- 校对者:Usey95, smilemuffie
TypeScript 3.0: unknown 类型
TypeScript 3.0 引入了新的unknown
类型,它是 any
类型对应的平安类型。
unknown
和 any
的次要区别是 unknown
类型会更加严格:在对 unknown
类型的值执行大多数操作之前,咱们必须进行某种模式的查看。而在对 any
类型的值执行操作之前,咱们不用进行任何查看。
这片文章次要关注于 unknown
类型的理论利用,以及蕴含了与 any
类型的比拟。如果须要更全面的代码示例来理解 unknown
类型的语义,能够看看 Anders Hejlsberg 的原始拉取申请。
any
类型
让咱们首先看看 any
类型,这样咱们就能够更好地了解引入 unknown
类型背地的动机。
自从 TypeScript 在 2012 年公布第一个版本以来 any
类型就始终存在。它代表所有可能的 JavaScript 值 — 根本类型,对象,数组,函数,Error,Symbol,以及任何你可能定义的值。
在 TypeScript 中,任何类型都能够被归为 any 类型。这让 any
类型成为了类型零碎的 顶级类型 (也被称作 全局超级类型)。
这是一些咱们赋值给 any
类型的代码示例:
let value: any;value = true; // OKvalue = 42; // OKvalue = "Hello World"; // OKvalue = []; // OKvalue = {}; // OKvalue = Math.random; // OKvalue = null; // OKvalue = undefined; // OKvalue = new TypeError(); // OKvalue = Symbol("type"); // OK
any
类型实质上是类型零碎的一个逃逸舱。作为开发者,这给了咱们很大的自在:TypeScript容许咱们对 any
类型的值执行任何操作,而无需当时执行任何模式的查看。
在上述例子中,变量 value
被定义成类型 any
。也是因而,TypeScript 认为以下所有操作都是类型正确的:
let value: any;value.foo.bar; // OKvalue.trim(); // OKvalue(); // OKnew value(); // OKvalue[0][1]; // OK
这许多场景下,这样的机制都太宽松了。应用any
类型,能够很容易地编写类型正确然而执行异样的代码。如果咱们应用 any
类型,就无奈享受 TypeScript 大量的爱护机制。
但如果能有顶级类型也能默认放弃平安呢?这就是 unknown
到来的起因。
unknown
类型
就像所有类型都能够被归为 any
,所有类型也都能够被归为 unknown
。这使得 unknown
成为 TypeScript 类型零碎的另一种顶级类型(另一种是 any
)。
这是咱们之前看到的雷同的一组赋值示例,这次应用类型为 unknown
的变量:
let value: unknown;value = true; // OKvalue = 42; // OKvalue = "Hello World"; // OKvalue = []; // OKvalue = {}; // OKvalue = Math.random; // OKvalue = null; // OKvalue = undefined; // OKvalue = new TypeError(); // OKvalue = Symbol("type"); // OK
对 value
变量的所有赋值都被认为是类型正确的。
当咱们尝试将类型为 unknown
的值赋值给其余类型的变量时会产生什么?
let value: unknown;let value1: unknown = value; // OKlet value2: any = value; // OKlet value3: boolean = value; // Errorlet value4: number = value; // Errorlet value5: string = value; // Errorlet value6: object = value; // Errorlet value7: any[] = value; // Errorlet value8: Function = value; // Error
unknown
类型只能被赋值给 any
类型和 unknown
类型自身。直观的说,这是有情理的:只有可能保留任意类型值的容器能力保留 unknown
类型的值。毕竟咱们不晓得变量 value
中存储了什么类型的值。
当初让咱们看看当咱们尝试对类型为 unknown
的值执行操作时会产生什么。以下是咱们之前看过的雷同操作:
let value: unknown;value.foo.bar; // Errorvalue.trim(); // Errorvalue(); // Errornew value(); // Errorvalue[0][1]; // Error
将 value
变量类型设置为 unknown
后,这些操作都不再被认为是类型正确的。通过扭转 any
类型到 unknown
类型,咱们的默认设置从容许所有翻转式的扭转成了简直什么都不容许。
这是 unknown
类型的次要价值主张:TypeScript 不容许咱们对类型为 unknown
的值执行任意操作。相同,咱们必须首先执行某种类型查看以放大咱们正在应用的值的类型范畴。
放大 unknown
类型范畴
咱们能够通过不同的形式将 unknown
类型放大为更具体的类型范畴,包含 typeof
运算符,instanceof
运算符和自定义类型爱护函数。所有这些放大类型范畴的技术都有助于 TypeScript 的基于控制流的类型剖析。
以下示例阐明了 value
如何在两个 if
语句分支中取得更具体的类型:
function stringifyForLogging(value: unknown): string { if (typeof value === "function") { // Within this branch, `value` has type `Function`, // so we can access the function's `name` property const functionName = value.name || "(anonymous)"; return `[function ${functionName}]`; } if (value instanceof Date) { // Within this branch, `value` has type `Date`, // so we can call the `toISOString` method return value.toISOString(); } return String(value);}
除了应用 typeof
或 instanceof
运算符之外,咱们还能够应用自定义类型爱护函数放大 unknown
类型范畴:
/** * A custom type guard function that determines whether * `value` is an array that only contains numbers. */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)) { // Within this branch, `unknownValue` has type `number[]`, // so we can spread the numbers as arguments to `Math.max` const max = Math.max(...unknownValue); console.log(max);}
只管 unknownValue
曾经被归为 unknown
类型,请留神它如何仍然在 if 分支下获取到 number[]
类型。
对 unknown
类型应用类型断言
在上一节中,咱们曾经看到如何应用 typeof
,instanceof
和自定义类型爱护函数来压服 TypeScript 编译器某个值具备某种类型。这是将 “unknown” 类型指定为更具体类型的平安且举荐的办法。
如果要强制编译器信赖类型为 unknown
的值为给定类型,则能够应用相似这样的类型断言:
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
变量值是一个数字, 但咱们假如它是一个字符串并应用类型断言 value as string
。所以请审慎应用类型断言!
联结类型中的 unknown
类型
当初让咱们看一下在联结类型中如何解决 unknown
类型。在下一节中,咱们还将理解穿插类型。
在联结类型中,unknown
类型会排汇任何类型。这就意味着如果任一组成类型是 unknown
,联结类型也会相当于 unknown
:
type UnionType1 = unknown | null; // unknowntype UnionType2 = unknown | undefined; // unknowntype UnionType3 = unknown | string; // unknowntype UnionType4 = unknown | number[]; // unknown
这条规定的一个意外是 any
类型。如果至多一种组成类型是 any
,联结类型会相当于 any
:
type UnionType5 = unknown | any; // any
所以为什么 unknown
能够排汇任何类型(any
类型除外)?让咱们来想想 unknown | string
这个例子。这个类型能够示意任何 unkown 类型或者 string 类型的值。就像咱们之前理解到的,所有类型的值都能够被定义为 unknown
类型,其中也包含了所有的 string
类型,因而,unknown | string
就是示意和 unknown
类型自身雷同的值集。因而,编译器能够将联结类型简化为 unknown
类型。
穿插类型中的 unknown
类型
在穿插类型中,任何类型都能够排汇 unknown
类型。这意味着将任何类型与 unknown
相交不会扭转后果类型:
type IntersectionType1 = unknown & null; // nulltype IntersectionType2 = unknown & undefined; // undefinedtype IntersectionType3 = unknown & string; // stringtype IntersectionType4 = unknown & number[]; // number[]type IntersectionType5 = unknown & any; // any
让咱们回顾一下 IntersectionType3
:unknown & string
类型示意所有能够被同时赋值给 unknown
和 string
类型的值。因为每种类型都能够赋值给 unknown
类型,所以在穿插类型中蕴含 unknown
不会扭转后果。咱们将只剩下 string
类型。
应用类型为 unknown
的值的运算符
unknown
类型的值不能用作大多数运算符的操作数。这是因为如果咱们不晓得咱们正在应用的值的类型,大多数运算符不太可能产生有意义的后果。
你能够在类型为 unknown
的值上应用的运算符只有四个相等和不等运算符:
===
==
!==
!=
如果要对类型为 unknown
的值应用任何其余运算符,则必须先指定类型(或应用类型断言强制编译器信赖你)。
示例:从 localStorage
中读取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
是一个被标记的联结类型。在其它语言中,它也能够被称作 Maybe
、Option
或者 Optional
。咱们应用 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
来示意反序列化失败,起因如下:
null
值是一个无效的 JSON 值。因而,咱们无奈辨别是对值null
进行了反序列化,还是因为短少参数或语法错误而导致整个操作失败。- 如果咱们从函数返回
null
,咱们无奈同时返回谬误。因而,咱们函数的调用者不晓得操作失败的起因。
为了完整性,这种办法的更成熟的代替计划是应用类型解码器进行平安的 JSON 解析。解码器须要咱们指定要反序列化的值的预期数据结构。如果长久化的JSON后果与该数据结构不匹配,则解码将以明确定义的形式失败。这样,咱们的函数总是返回无效或失败的解码后果,就不再须要 unknown
类型了。