关于javascript:TypeScript-之常见类型下

1次阅读

共计 7096 个字符,预计需要花费 18 分钟才能阅读完成。

TypeScript 的官网文档早已更新,但我能找到的中文文档都还停留在比拟老的版本。所以对其中新增以及订正较多的一些章节进行了翻译整顿。

本篇翻译整顿自 TypeScript Handbook 中「Everyday Types」章节。

本文并不严格依照原文翻译,对局部内容也做了解释补充。

类型别名(Type Aliases)

咱们曾经学会在类型注解里间接应用对象类型和联结类型,这很不便,但有的时候,一个类型会被应用屡次,此时咱们更心愿通过一个独自的名字来援用它。

这就是类型别名(type alias)。所谓类型别名,顾名思义,一个能够指代任意类型的名字。类型别名的语法是:

type Point = {
  x: number;
  y: number;
};
 
// Exactly the same as the earlier example
function printCoord(pt: Point) {console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
 
printCoord({x: 100, y: 100});

你能够应用类型别名给任意类型一个名字,举个例子,命名一个联结类型:

type ID = number | string;

留神别名是惟一的别名,你不能应用类型别名创立同一个类型的不同版本。当你应用类型别名的时候,它就跟你编写的类型是一样的。换句话说,代码看起来可能不非法,但对 TypeScript 仍然是非法的,因为两个类型都是同一个类型的别名:

type UserInputSanitizedString = string;
 
function sanitizeInput(str: string): UserInputSanitizedString {return sanitize(str);
}
 
// Create a sanitized input
let userInput = sanitizeInput(getInput());
 
// Can still be re-assigned with a string though
userInput = "new input";

接口(Interfaces)

接口申明(interface declaration)是命名对象类型的另一种形式:

interface Point {
  x: number;
  y: number;
}
 
function printCoord(pt: Point) {console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
 
printCoord({x: 100, y: 100});

就像咱们在上节应用的类型别名,这个例子也同样能够运行,就跟咱们应用了一个匿名对象类型一样。TypeScript 只关怀传递给 printCoord 的值的构造(structure)——关怀值是否有冀望的属性。正是这种只关怀类型的构造和能力的个性,咱们才认为 TypeScript 是一个结构化(structurally)的类型零碎。

类型别名和接口的不同

类型别名和接口十分类似,大部分时候,你能够任意抉择应用。接口的简直所有个性都能够在 type 中应用,两者最要害的差异在于类型别名自身无奈增加新的属性,而接口是能够扩大的。

// Interface
// 通过继承扩大类型
interface Animal {name: string}

interface Bear extends Animal {honey: boolean}

const bear = getBear() 
bear.name
bear.honey
        
// Type
// 通过交加扩大类型
type Animal = {name: string}

type Bear = Animal & {honey: boolean}

const bear = getBear();
bear.name;
bear.honey;
// Interface
// 对一个曾经存在的接口增加新的字段
interface Window {title: string}

interface Window {ts: TypeScriptAPI}

const src = 'const a ="Hello World"';
window.ts.transpileModule(src, {});
        
// Type
// 创立后不能被扭转
type Window = {title: string}

type Window = {ts: TypeScriptAPI}

// Error: Duplicate identifier 'Window'.

在后续的章节里,你还会理解的更多。所以上面这些内容不能立即了解也没有关系:

  • 在 TypeScript 4.2 以前,类型别名的名字可能会呈现在报错信息中,有时会代替等价的匿名类型(兴许并不是冀望的)。接口的名字则会始终呈现在错误信息中。
  • 类型别名兴许不会实现申明合并,然而接口能够
  • 接口可能只会被用于申明对象的形态,不能重命名原始类型
  • 接口通过名字应用的时候,他们的名字会总是呈现在错误信息中,如果间接应用,则会呈现原始构造

大部分时候,你能够依据集体爱好进行抉择。TypeScript 会通知你它是否须要其余形式的申明。如果你喜爱探索性的应用,那就应用 interface,直到你须要用到 type 的个性。

类型断言(Type Assertions)

有的时候,你晓得一个值的类型,但 TypeScript 不晓得。

举个例子,如果你应用 document.getElementById,TypeScript 仅仅晓得它会返回一个 HTMLElement,然而你却晓得,你要获取的是一个 HTMLCanvasElement

这时,你能够应用类型断言将其指定为一个更具体的类型:

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

就像类型注解一样,类型断言也会被编译器移除,并且不会影响任何运行时的行为。

你也能够应用尖括号语法(留神不能在 .tsx 文件内应用),是等价的:

const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

谨记:因为类型断言会在编译的时候被移除,所以运行时并不会有类型断言的查看,即便类型断言是谬误的,也不会有异样或者 null 产生。

TypeScript 仅仅容许类型断言转换为一个更加具体或者更不具体的类型。这个规定能够阻止一些不可能的强制类型转换,比方:

const x = "hello" as number;
// Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

有的时候,这条规定会显得十分激进,阻止了你本来无效的类型转换。如果产生了这种事件,你能够应用双重断言,先断言为 any(或者是 unknown),而后再断言为冀望的类型:

const a = (expr as any) as T;

字面量类型(Literal Types)

除了常见的类型 stringnumber,咱们也能够将类型申明为更具体的数字或者字符串。

家喻户晓,在 JavaScript 中,有多种形式能够申明变量。比方 varlet,这种形式申明的变量后续能够被批改,还有 const,这种形式申明的变量则不能被批改,这就会影响 TypeScript 为字面量创立类型。

let changingString = "Hello World";
changingString = "Olá Mundo";
// Because `changingString` can represent any possible string, that
// is how TypeScript describes it in the type system
changingString;
// let changingString: string
const constantString = "Hello World";
// Because `constantString` can only represent 1 possible string, it
// has a literal type representation
constantString;
// const constantString: "Hello World"

字面量类型自身并没有什么太大用:

let x: "hello" = "hello";
// OK
x = "hello";
// ...
x = "howdy";
// Type '"howdy"' is not assignable to type '"hello"'.

如果联合联结类型,就显得有用多了。举个例子,当函数只能传入一些固定的字符串时:

function printText(s: string, alignment: "left" | "right" | "center") {// ...}
printText("Hello, world", "left");
printText("G'day, mate","centre");
// Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.

数字字面量类型也是一样的:

function compare(a: string, b: string): -1 | 0 | 1 {return a === b ? 0 : a > b ? 1 : -1;}

当然了,你也能够跟非字面量类型联结:

interface Options {width: number;}
function configure(x: Options | "auto") {// ...}
configure({width: 100});
configure("auto");
configure("automatic");

// Argument of type '"automatic"' is not assignable to parameter of type 'Options |"auto"'.

还有一种字面量类型,布尔字面量。因为只有两种布尔字面量类型,truefalse,类型 boolean 实际上就是联结类型 true | false 的别名。

字面量推断(Literal Inference)

当你初始化变量为一个对象的时候,TypeScript 会假如这个对象的属性的值将来会被批改,举个例子,如果你写下这样的代码:

const obj = {counter: 0};
if (someCondition) {obj.counter = 1;}

TypeScript 并不会认为 obj.counter 之前是 0,当初被赋值为 1 是一个谬误。换句话说,obj.counter 必须是 string 类型,但不要求肯定是 0,因为类型能够决定读写行为。

这也同样利用于字符串:

declare function handleRequest(url: string, method: "GET" | "POST"): void;

const req = {url: "https://example.com", method: "GET"};
handleRequest(req.url, req.method);

// Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

在下面这个例子里,req.method 被推断为 string,而不是 "GET",因为在创立 req 和 调用 handleRequest 函数之间,可能还有其余的代码,或者会将 req.method 赋值一个新字符串比方 "Guess"。所以 TypeScript 就报错了。

有两种形式能够解决:

  1. 增加一个类型断言扭转推断后果:
// Change 1:
const req = {url: "https://example.com", method: "GET" as "GET"};
// Change 2
handleRequest(req.url, req.method as "GET");

批改 1 示意“我无意让 req.method 的类型为字面量类型 "GET",这会阻止将来可能赋值为 "GUESS" 等字段”。批改 2 示意“我晓得 req.method 的值是 "GET"”.

  1. 你也能够应用 as const 把整个对象转为一个类型字面量:
const req = {url: "https://example.com", method: "GET"} as const;
handleRequest(req.url, req.method);

as const 成果跟 const 相似,然而对类型零碎而言,它能够确保所有的属性都被赋予一个字面量类型,而不是一个更通用的类型比方 string 或者 number

nullundefined

JavaScript 有两个原始类型的值,用于示意空缺或者未初始化,他们别离是 nullundefined

TypeScript 有两个对应的同名类型。它们的行为取决于是否关上了 strictNullChecks 选项。

strictNullChecks 敞开

当 strictNullChecks 选项敞开的时候,如果一个值可能是 null 或者 undefined,它仍然能够被正确的拜访,或者被赋值给任意类型的属性。这有点相似于没有空值查看的语言 (比方 C#,Java)。这些查看的短少,是导致 bug 的次要源头,所以咱们始终举荐开发者开启 strictNullChecks 选项。

strictNullChecks 关上

当 strictNullChecks 选项关上的时候,如果一个值可能是 null 或者 undefined,你须要在用它的办法或者属性之前,先查看这些值,就像用可选的属性之前,先检查一下 是否是 undefined,咱们也能够应用类型收窄(narrowing)查看值是否是 null

function doSomething(x: string | null) {if (x === null) {// do nothing} else {console.log("Hello," + x.toUpperCase());
  }
}

非空断言操作符(后缀 !)(Non-null Assertion Operator)

TypeScript 提供了一个非凡的语法,能够在不做任何查看的状况下,从类型中移除 nullundefined,这就是在任意表达式前面写上 !,这是一个无效的类型断言,示意它的值不可能是 null 或者 undefined

function liveDangerously(x?: number | null) {
  // No error
  console.log(x!.toFixed());
}

就像其余的类型断言,这也不会更改任何运行时的行为。重要的事件说一遍,只有当你明确的晓得这个值不可能是 null 或者 undefined 时才应用 !

枚举(Enums)

枚举是 TypeScript 增加的新个性,用于形容一个值可能是多个常量中的一个。不同于大部分的 TypeScript 个性,这并不是一个类型层面的增量,而是会增加到语言和运行时。因为如此,你应该理解下这个个性。然而能够等一等再用,除非你确定要应用它。你能够在枚举类型页面理解更多的信息。

不常见的原始类型(Less Common Primitives)

咱们提一下在 JavaScript 中残余的一些原始类型。然而咱们并不会深刻解说。

bigInt

ES2020 引入原始类型 BigInt,用于示意十分大的整数:

// Creating a bigint via the BigInt function
const oneHundred: bigint = BigInt(100);
 
// Creating a BigInt via the literal syntax
const anotherHundred: bigint = 100n;

你能够在 TypeScript 3.2 的公布日志中理解更多信息。

symbol

这也是 JavaScript 中的一个原始类型,通过函数 Symbol(),咱们能够创立一个全局惟一的援用:

const firstName = Symbol("name");
const secondName = Symbol("name");
 
if (firstName === secondName) {
  // This condition will always return 'false' since the types 'typeof firstName' and 'typeof secondName' have no overlap.
  // Can't ever happen
}

你能够在 Symbol 页面理解更多的信息。

TypeScript 系列

  1. TypeScript 之 根底入门
  2. TypeScript 之 常见类型(上)
  3. TypeScript 之 类型收窄
  4. TypeScript 之 函数
  5. TypeScript 之 对象类型
  6. TypeScript 之 泛型
  7. TypeScript 之 Keyof 操作符
  8. TypeScript 之 Typeof 操作符
  9. TypeScript 之 索引拜访类型
  10. TypeScript 之 条件类型

微信:「mqyqingfeng」,加我进冴羽惟一的读者群。

如果有谬误或者不谨严的中央,请务必给予斧正,非常感激。如果喜爱或者有所启发,欢送 star,对作者也是一种激励。

正文完
 0