TypeScript之常用类型

原始类型:string,number,boolean

javaScript 有三个经常会使用到的原始类型stirngnumber,boolean。每一个在Typescript都有一个相对应的类型。 正如你所期望的,你可以得到相同的名称在javascript中使用typeof去操作这些类型对应的

  1. string代表的值,例如"Hello,world"。
  2. number代表的值,例如42
  3. boolean代表的两个值,truefalse

数组(Arrays)

若要指定类似[1,2,3]的数组类型,你可以使用number[]语法,这个语法可以声明任何类型的数组(e.g. string[])。或许你也看到过Array<number>的这种相同含义写法,

请注意[number]是元组(Tuples);请移步到Tuple

Any类型

TypeScript也有一个特殊的类型----any。任何时候,你不想因为类型问题而造成类型检查报错的时候,你都可以去使用它。
当你在一个值上使用any的时候,你可以在这个值上访问任何属性(访问的属性也是any类型),像function一样调用它,把任意类型的值都赋值给它。只要在合法的语法内,可以做相当多的事情:

let obj: any = { x: 0 };
// None of the following lines of code will throw compiler errors.
// Using `any` disables all further type checking, and it is assumed 
// you know the environment better than TypeScript.
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;

当你不想只是用写一长串类型去使TypeScript相信一行代码时,any类型非常有用。

noImplicitAny

当你不指定一个类型,并且TypeScript无法从上下文推断除类型的话,编译器一般会默认推断为any。通常情况下,如果你需要避免这种情况的话,因为any不会类型检查,这就需要使用编译器上的配置noImplicitAny去标识任何隐藏的any类型为错误(让编译器报错)。

在变量上使用类型注释

当你使用constvarlet去声明变量的时候,你可以选择直接指定类型:

let myName: string = "Alice";

TypeScript不使用"左侧类型(types on the left)"的风格,就像int x = 0;类型注释往往放在被注释的对象后面

然而通常情况下,这并不是必要的。尽可能的情况下,TypeScirpt会在你的代码上尝试自动推断类型。举个例子,变量的类型会被基于它的初始化类型进行推断:

// No type annotation needed -- 'myName' inferred as type 'string'
let myName = "Alice";

在大多数情况下,你不需要刻意的去学习推断规则,尝试使用更少的类型注释

函数(Functions)

Functions是JavaScript传递数据的主要工具。TypeScript允许你指定function的输入输出类型。

参数类型注释(Parameter Type Annotations)

当你定义了一个function ,你可以在function的每个参数后面添加类型注释,参数的类型定义在参数名称后面

// Parameter type annotation
function greet(name: string) {
 console.log("Hello, " + name.toUpperCase() + "!!");
}

当参数添加了类型注释,调用function传递参数时,将会被检查:

// Would be a runtime error if executed!
greet(42);

甚至你没有在你的参数后面进行类型声明,TypeScript仍然会检查你传递的参数数量是否正确

返回类型注释(Return Type Annotations)

你也可以添加返回类型注释返回类型注释出现在参数列表之后:

function getFavoriteNumber(): number {
 return 26;
}

很类似变量类型注释,你通常不需要显式的添加一个返回类型注释,因为TypeScript会基于functionreturn语句,自动推导function的返回类型。这个类型注解上述的类型注解的例子并没有改变任何东西。有一些代码库会为了文档的目的显式的返回一个类型,去防止意外的更改,或者只是个人的喜好。

之后,我们将看到更多关于上下文影响其类型的例子。

匿名函数(Anonymous Functions)

匿名函数有略微不同于函数声明,当一个函数出现在TypeScript可以明确的知道它怎么被调用的地方,那么它的参数会自动推到类型。

举个例子:

// No type annotations here, but TypeScript can spot the bug
const names = ["Alice", "Bob", "Eve"];
// Contextual typing for function
names.forEach(function (s) {
 console.log(s.toUppercase()); // 被string[]类型的数组调用,自动推导为string类型
 // Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
// Contextual typing also applies to arrow functions
names.forEach((s) => {
 console.log(s.toUppercase()); // 被string[]类型的数组调用,自动推导为string类型
 // Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});

上述例子中,甚至参数s都没有类型注释TypeScript使用了forEach函数的类型,和数组的推导类型一起明确了参数s的类型。

这种处理方式被称为上下文类型,因为函数的上下文通知参数s应该有什么类型。

类似于推到规则,你不需要特别学习它是怎么发生的。但是需要明白这种情况下,它会发生,这样可以帮助你确认什么时候不需要类型注解

对象类型(Object Types)

作为原始类型的一部分,你最经常遇到的类型是对象类型(Object Type),JavaScript中有属性的值几乎都对应了它!我们可以简单的去列出对象的属性和属性的类型去定义对象类型(Object Type)

举个例子,函数携带了一个坐标对象作为参数:

// The parameter's type annotation is an object type
function printCoord(pt: { x: number; y: number }) {
 console.log("The coordinate's x value is " + pt.x);
 console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });

上面的例子中,我们声明了拥有两个number类型的属性的参数,属性是xy。你可以使用,或者;(推荐)去分割属性。并且最后一个分隔符是可选的。

每个属性的类型部分也是可选的。如果你不显式指定一个类型,它就会被假定为any

可选属性(Options Properties)

对象类型也可以指定他们的某些属性是可选的(optional),使用`可选时`,需要在属性名称后面加?符号:

function printName(obj: { first: string; last?: string }) {
 // ...
}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });

javaScript中,如果你访问一个不存在的属性,它会返回一个undefined而不是抛出错误。如此,当你访问可选属性之前,你必须检查它是否是undefined

function printName(obj: { first: string; last?: string }) {
 // Error - might crash if 'obj.last' wasn't provided!
 console.log(obj.last.toUpperCase());
 //Object is possibly 'undefined'.
 if (obj.last !== undefined) {
 // OK
 console.log(obj.last.toUpperCase());
 }
 // A safe alternative using modern JavaScript syntax:
 console.log(obj.last?.toUpperCase());
}

联合类型(Union Types)

TypeScript的类型系统允许你使用各种各样的运算符从现有类型去创建一个新类型。现在让我们来写一些新类型。是时候开始用一种有趣的方式去组合他们。

定义一个联合类型(Define a Union Type)

第一种组合方式是联合类型。一个联合类型由多个类型组织而成代表值是这些类型中任意的一个。我们引用每个类型作为联合类型的成员。

让我们写一个可以操作string或者number类型的函数:

function printId(id: number | string) {
 console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// Error
printId({ myID: 22342 });
// Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.

使用联合类型(Working with Union Types)

去匹配联合类型的值是非常容易的----就是简单的提供联合类型中的一个。如果你有一个联合类型,你将如何使用它。

TypeScript只允许使用对联合类型的每个成员都有效的操作。举个例子,如果你有一个string | number的联合类型,那你不能使用只有string类型才有的方法:

function printId(id: number | string) {
 console.log(id.toUpperCase());
 // Property 'toUpperCase' does not exist on type 'string | number'.
 // Property 'toUpperCase' does not exist on type 'number'.
}

解决上述情况的方式是通过代码来收缩联合类型(narrow the union with code ),和你在javaScript中不使用类型注释一样。当TypeScript可以根据代码结构推断更具体的类型时,就会发生收缩。

举个例子,TypeScript知道只有string的值typeof的值才是"string"

function printId(id: number | string) {
 if (typeof id === "string") {
 // In this branch, id is of type 'string'
 console.log(id.toUpperCase());
 } else {
 // Here, id is of type 'number'
 console.log(id);
 }
}

另一个例子是使用函数例如Array.isArray

function welcomePeople(x: string[] | string) {
 if (Array.isArray(x)) {
 // Here: 'x' is 'string[]'
 console.log("Hello, " + x.join(" and "));
 } else {
 // Here: 'x' is 'string'
 console.log("Welcome lone traveler " + x);
 }
}

请注意在else分支中,我们不需要做任何事情,如果x不是string[]类型,那它在else中必定是string类型。

又是你会遇到一个成员拥有公共方法的联合类型。举个例子。数组和字符串这两个都勇敢又slice方法。如果联合类型中的每个成员都有相同的方法,那你可以在不使用收缩(narrowing)的情况下使用这个使用这个属性:

// Return type is inferred as number[] | string
function getFirstThree(x: number[] | string) {
 return x.slice(0, 3);
}

联合属性拥有交集(intersection)是有点迷惑的。这不是偶然-这个名称`unio` 来自类型导论。联合类型number | string是由每个类型的并集组成。请注意,给定了两个集合,每个集合都有相应的实现。只有这些交集的实现才适用于集合本身的并集。例如,如果我们有一个房间里的高个子戴着帽子,另一个房间里的讲西班牙语的人戴着帽子,把这两个房间结合起来,我们对每个人的唯一了解就是他们一定戴着帽子。

类型别名(Type Aliases)

我们一般是使用对象类型(object types)和联合类型都是直接他们写成类型注解。这非常方便。但是常见的情况是,不止一次地使用同一个类型,并通过一个名称引用它。

一个类型别名准确的来说,就是一个名称代表了任何类型。类型别名的语法是:

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是声明对象类型的另一种方式:

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 仅仅关心我们传输的值的结构,它只是关注它期望的有的属性。仅关注类型的结构和功能是我们将TypeScript称为结构化类型系统的原因。

类型别名和接口的不同(Differences Between Type Aliases and Interfaces)

类型别名和接口非常的类似,并且很多时候你可以自由的去使用它们。大多数interface的特性在type中也是起作用的,关键区别在于,类型不能重新打开以添加新属性,而接口总是可扩展的。

// Extending an interface
interface Animal {
 name: string
}
interface Bear extends Animal {
 honey: boolean
}
const bear = getBear() 
bear.name
bear.honey
// Extending a type via intersections
type Animal = {
 name: string
}
type Bear = Animal & { 
 honey: boolean 
}
const bear = getBear();
bear.name;
bear.honey;
// Adding new fields to an existing interface
interface Window {
 title: string
}
interface Window {
 ts: TypeScriptAPI
}
const src = 'const a = "Hello World"';
window.ts.transpileModule(src, {});
// A type cannot be changed after being created
type Window = {
 title: string
}
type Window = {
 ts: TypeScriptAPI
}
 // Error: Duplicate identifier 'Window'.

在之后你将学习这些概念,所以现在不用担心你不明白。

类型断言(Type Assertions)

有时您会有 TypeScript 无法知道的值类型的信息。

举个例子,如果你使用了document.getElementByIdTypeScript仅仅知道它会返回HTMLElement类型中的一个,但是你或许知道你的页面拥有一个给了ID的HTMLCanvasElement。

在这种情况下,你可以使用一个类型断言(type assertions)去指定一个更明确的类型:

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

作为类型断言,类型断言会被编译器移除不会在运行时影响你的代码行为。你可以使用尖括号语法(angle-bracket):

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.

这里举个例子:

type Bob = {
 weight: number;
};
type Lucy = {
 weight: number;
 height: number;
};
type John = {};
const humanA: Bob = { weight: 0 };
const humanB = humanA as Lucy; // 对于Bob来说,Lucy更宽
const humanC = humanA as John; // 对于Bob来说,John更窄

字面量类型(Literal Types)

除了常规类型stringnumber类型之外,我们可以在类型韦志中引用特定的字符串数字

考虑这个问题的一种方法是考虑javaScript是如何以不同的方式声明变量的。varlet这两个都允许改变保存在变量中的值,但是const不是。这个反映了TypeScript是如何为字面量创建类型的。

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

文字类型本身并不十分有价值:

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;
}

当然,你可以和不是字面量的类型结合使用:

nterface 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"'.

还有很多其它的字面量类型:boolean 字面量。boolean字面量类型只有两种值,就如你所猜测的一样。boolean的两种值是truefalse。类型boolean他本身实际上也是true | false的别名。

字面量推导(Literal Inference)

当你组织一个对象变量的时候,TypeScript会自动假设这个对象的属性稍后可能更改值。例如,如果你的代码像这样:

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

TypeScript并没有认为先前为0的字段赋值为1是一个错误。而是告诉obj.counter必须是number类型,不是0,因为这个类型的变量确定可以读和写

相同的应用在string类型上:

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");

Change 1 代表“打算req.method一直为字面量类型"GET"”,防止之后被篡改。Change2代表”你确认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选项

枚举(Enums)

枚举是由 TypeScript 添加到 JavaScript 中的一个特性,它允许描述一个值,该值可以是一组可能的命名常量中的一个。与大多数 TypeScript 特性不同,这不是对 JavaScript 的类型级别添加,而是添加到语言和运行时中。因此,您应该知道这个特性的存在,但是除非您确定,否则可能不要使用它。

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

值得一提的是类型系统中还有JavaScript中剩余的一些对应的类型。这里就不深入了解了。

bigint

在ES2020中,JavaScript中用来表示非常大的整数的原始类型BigInt

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

symbol

在JavaScript用来创建全局唯一的引用的函数Symbol():

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

参考everyday type

作者:RadiumAg

%s 个评论

要回复文章请先登录注册