TypeScript

TypeScript的数据类型

2020-10-09  本文已影响0人  浅忆_0810

TypeScipt中为了使编写的代码更加规范,更有利于维护,增加了类型校验,所以以后写ts代码必须要指定类型

TypeScript后面可以不指定类型,但是后期不能改变其类型,不然会报错,但是只会报错,不会阻止代码编译,因为JS是可以允许的


1. 基本类型

1.1 布尔类型(boolean)

let flag: boolean = true;
flag = 123; // 报错,因为不符合类型
flag = false; // 正确写法,boolean类型只能写 true 和 false

1.2 数字类型(number)

let num: number = 123;
console.log(num);

1.3 字符串类型(string)

let str: string = "string";
console.log(str);

1.4 数组类型(Array)

TypeScript中有两种定义数组的方法

// 第一种定义数组方法
let arr1: number[] = [1.2.3]; // 一个全是数字的数组

// 第二种定义数组方法
let arr2: Array<number> = [1,2,3];

1.5 只读数组类型(ReadonlyArray)

只读数组中的数组成员和数组本身的长度等属性都不能够修改,并且也不能赋值给原赋值的数组

let a: number[] = [1,2,3,4];
let ro: ReadonlyArray<number> = a; // 其实只读数组只是一个内置定义的泛型接口
ro[1] = 5; // 报错
ro.length = 5; // 报错
a = ro; // 报错,因为ro的类型为Readonly,已经改变了类型
a = ro as number[]; // 正确,不能通过上面的方法复制,但是可以通过类型断言的方式

类型断言见此链接

1.6 元组类型(tuple)

元组类型属于数组的一种,上面一种数组里面只能有一种类型,否则会报错,而元组类型内部可以有多种类型

// 元组类型可以为数组中的每个成员指定一个对应的类型
let arr: [number,string] = [123,"string"]; // 注意如果类型不对应也会报错
arr[2] = 456; // 报错
// 因为元组类型在声明时是一一对应的,这里只能有2个成员

1.7 枚举类型(enum)

/*
  通过:
    enum 枚举名{
      标识符 = 整形常数,
      标识符 = 整形常数,
      ......
      标识符 = 整形常数
    };
    定义一个枚举类型
*/
enum Color {
  Red = 1,
  Blue = 3,
  Oragne = 5
}

let color: Color = Color.Red; // color为Color枚举类型,值为Color中的Red,也就是1
console.log(color); // 1

注意:

enum Color {
  Red,
  Blue = 3,
  "Oragne"
}

let color1: Color = Color.Red;
let color2: Color = Color.Blue;
let color3: Color = Color.Oragne;
let color4: string = Color[0]; // 通过获取索引得到标识符

console.log(color1); // 0
console.log(color2); // 3
console.log(color3); // 4
console.log(color4); // red

1.8 任意类型(any)

任意类型的数据就像JS一样,可以随意改变其类型

let num:any = 123;
num = "string";
console.log(num); // string

具体用法

// 如果在ts中想要获取 DOM 节点,就需要使用任意类型
let oDiv: any = document.getElementsByTagName("div")[0];
oDiv.style.backgroundColor = "red";
// 按道理说DOM节点应该是个对象,但是TypeScript中没有对象的基本类型,所以必须使用 any 类型才不会报错

1.9 undefinednull类型

虽然为变量指定了类型,但是如果不赋值的话默认该变量还是undefined类型,如果没有指定undefined直接使用该变量的话会报错,只有自己指定的是undefined类型才不会报错

let flag1: undefined; // 也可以 let flag1: undefined = undefined;
console.log(flag); // undefined

let flag2: null = null; // 如果不指定值为null那么打印的时候会报错
console.log(flag2); // null

为变量指定多种可能的类型

let flag: number | undefined; // 这种写法就不会在没有赋值的时候报错了,因为设置了可能为undefined
console.log(flag); // undefined
flag = 123;
console.log(flag); // 123 也可以改为数值类型

// 也可以设定多个类型
let flag1: number | string | null | undefined;
flag1 = 123;
console.log(flag1); // 123
flag1 = null;
console.log(flag1); // null
flag1 = "string";
console.log(flag1); // string

1.10 void类型

TypeScript中的void表示没有任何类型,一般用于定义方法的时候没有返回值,虽然也能给变量指定类型,但是void类型只能被赋值undefinednull,没有什么实际意义

TypeScript中函数的类型表示其返回值的类型,函数类型为void表示其没有返回值

function run(): void {
  console.log(123);
  // return 不能有返回值,否则报错
}

function run1(): number {
  console.log(456);
  return 123; // 必须有返回值,并且返回值为number类型,否则报错
}

function run2(): any {
  console.log(789); // 因为any是任意类型,所以也可以不要返回值
}

1.11 never类型

never类型是其他类型(包括nullundefine)的子类型,代表着从来不会出现的值,意味着声明never类型的变量只能被never类型所赋值

let a: undefined;
a = undefined;

let b: null;
b = null;

let c: never; // c不能被任何值赋值,包括 null 和 undefiend
c = (() => {
  throw new Error("error!!");
})(); // 可以这样赋值

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
  throw new Error(message);
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
  while (true) {
  }
}

// 推断的返回值类型为never
function fail() {
  return error("Something failed");
}

1.12 object类型

object表示非原始类型,也就是除numberstringbooleansymbolnullundefined之外的类型

let stu: object = {name:"张三", age:18};
console.log(stu); // {name: "张三", age: 18}

// 也可以
let stu: {} = { name: "张三", age: 18 };
console.log(stu); // {name: "张三", age: 18}
declare function create (o: object | null): void;
// declare是一个声明的关键字,可以写在声明一个变量时解决命名冲突的问题
create({ prop: 0 }); // OK
create(null); // OK

create(42); // 报错
create("string"); // 报错
create(false); // 报错
create(undefined); // 报错

2. 高级类型

2.1 交叉类型

交叉类型是将多个类型合并为一个类型。这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性

function extend<T, U>(first: T, second: U): T & U {
  let result = <any>{}; // 官方这里是T&U,但是会报错,因为不知T和U是不是对象,必须any才可以

  for (let id in first) {
    (<any>result)[id] = (<any>first)[id];
  }
  for (let id in second) {
    if (!result.hasOwnProperty(id)) {
      (<any>result)[id] = (<any>second)[id];
    }
  }

  return result;
}

class Person {
  constructor(public name: string) { }
}
interface Loggable {
  log(): void;
}
class ConsoleLogger implements Loggable {
  log() {
    // ...
  }
}

var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();

2.2 联合类型

联合类型与交叉类型很有关联,但是使用上却完全不同,我们使用的用竖线隔开每一个类型参数的时候使用的就是联合类型

联合类型表示一个值可以是几种类型之一。 用竖线( |)分隔每个类型,所以 number | string | boolean表示一个值可以是 numberstring,或 boolean

function padLeft(value: string, padding: string | number) {
  // ...
}

let indentedString = padLeft("Hello world", true); // errors,只能是string 或 number

如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员

interface Bird {
  fly();
  layEggs();
}

interface Fish {
  swim();
  layEggs();
}

function getSmallPet(): Fish | Bird { // 这的报错先不管
  // ...
}

let pet = getSmallPet(); // 因为没有明确返回哪个类型,所以只能确定时Fish和Bird类型中的一个
pet.layEggs(); // 我们可以调用共有的方法
pet.swim();    // errors,不能调用一个类型的方法,因为万一是Bird类型就不行了

2.3 类型保护

联合类型适合于那些值可以为不同类型的情况。 但当我们想确切地了解是否为 Fish时,JavaScript里常用来区分2个可能值的方法是检查成员是否存在,但是在TypeScript中必须要先使用类型断言

let pet = getSmallPet();

if ((<Fish>pet).swim) {
  (<Fish>pet).swim();
} else {
  (<Bird>pet).fly();
}
2.3.1 用户自定义类型保护
function isFish(pet: Fish | Bird): pet is Fish {
  return (<Fish>pet).swim !== undefined; // 返回一个布尔值,然后TypeScript会缩减该变量类型
}
/*
  pet is Fish就是类型谓词。谓词为 parameterName is Type 这种形式,parameterName必须是来自于当前函数签名里的一个参数名
*/
// 'swim' 和 'fly' 调用都没有问题了,因为缩减了pet的类型
if (isFish(pet)) {
  pet.swim();
} else {
  pet.fly();
}

TypeScript不仅知道在 if分支里 petFish类型,它还清楚在 else分支里,一定 不是 Fish类型,一定是 Bird类型

2.3.2 typeof类型保护
function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
    return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
    return padding + value;
  }
    
  throw new Error(`Expected string or number, got '${padding}'.`);
}

这些 typeof类型保护只有两种形式能被识别:typeof v === "typename"typeof v !== "typename""typename"必须是 "number""string""boolean""symbol"。 但是TypeScript并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护

2.3.3 instanceof类型保护
interface Padder {
  getPaddingString(): string
}

class SpaceRepeatingPadder implements Padder {
  constructor(private numSpaces: number) { }
  getPaddingString() {
    return Array(this.numSpaces + 1).join(" ");
  }
}

class StringPadder implements Padder {
  constructor(private value: string) { }
  getPaddingString() {
    return this.value;
  }
}

function getRandomPadder() {
  return Math.random() < 0.5
    ? new SpaceRepeatingPadder(4)
    : new StringPadder(" ");
}

// 类型为SpaceRepeatingPadder | StringPadder
let padder: Padder = getRandomPadder();

if (padder instanceof SpaceRepeatingPadder) {
  padder; // 类型细化为'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
  padder; // 类型细化为'StringPadder'
}

instanceof的右侧要求是一个构造函数,TypeScript将细化为:

  • 此构造函数的 prototype属性的类型,如果它的类型不为 any的话
  • 构造签名所返回的类型的联合
2.3.4 可以为null的类型

如果没有在vscode中,直接编译的话是可以给一个其他类型的值赋值为undefined或者null的,但是如果编译时使用了--strictNullChecks标记的话,就会和vscode一样不能赋值了,并且可选参数和可以选属性会自动加上| undefined类型

2.3.5 类型保护与类型断言

由于可以为null的类型是通过联合类型实现,那么需要使用类型保护来去除 null

function f(sn: string | null): string {
  if (sn === null) {
    return "default";
  } else {
    return sn;
  }
}

// 也可以使用下面的形式
function f(sn: string | null): string {
  return sn || "default";
}

如果编译器不能够去除 nullundefined,你可以使用类型断言手动去除。 语法是添加 !后缀:identifier!identifier的类型里去除了 nullundefined

function broken(name: string | null): string {
  function postfix(epithet: string) {
    return name.charAt(0) + '.  the ' + epithet; // error, 'name' is possibly null
  }
  name = name || "Bob";

  return postfix("great");
}

// 上面的函数会报错
function fixed(name: string | null): string {
  function postfix(epithet: string) {
    // 这里的name强制断言不是null或者undefined,因为在嵌套函数中不知道name是否为null
    return name!.charAt(0) + '.  the ' + epithet; // ok
  }
  name = name || "Bob"; // 这里已经明确表明返回的name肯定是字符串
  
  return postfix("great"); // 但是由于是嵌套函数,这里还不知道
}
/*
  嵌套函数中: 因为编译器无法去除嵌套函数的null(除非是立即调用的函数表达式)。因为它无法跟踪所有对嵌套函数的调用,尤其是将内层函数做为外层函数的返回值。如果无法知道函数在哪里被调用,就无法知道调用时name的类型
*/

2.4 类型别名

类型别名会给一个类型起个新名字。类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何需要手写的类型

别名不会创建一个新的类型,而是创建了一个新的名字来引用那个类型

type Name = string; // 通过type关键词创建一个别名
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
  if (typeof n === 'string') {
    return n;
  } else {
    return n();
  }
}

同接口一样,类型别名也可以是泛型,可以添加类型参数并且在别名声明的右侧传入

type Container<T> = { value: T };

// 我们也可以使用类型别名来在属性里引用自己
type Tree<T> = {
  value: T;
  left: Tree<T>;
  right: Tree<T>;
}

// 与交叉类型一起使用,我们可以创建出一些十分稀奇古怪的类型
type LinkedList<T> = T & { next: LinkedList<T> };

interface Person {
  name: string;
}

var people: LinkedList<Person>;
var s = people.name; // 注意这种写法在vscode中会报错,因为更加严格,必须先赋值再使用
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;

类型别名不能出现在声明右侧的任何地方

type Yikes = Array<Yikes>; // 报错

2.5 字符串自变量类型

字符串字面量类型允许指定字符串必须的固定值。在实际应用中,字符串字面量类型可以与联合类型、类型保护和类型别名很好的配合。通过结合使用这些特性,可以实现类似枚举类型的字符串

type Easing = "ease-in" | "ease-out" | "ease-in-out";

class UIElement {
  animate(dx: number, dy: number, easing: Easing) {
    if (easing === "ease-in") {
      // ...
    } else if (easing === "ease-out") {
    } else if (easing === "ease-in-out") {
    } else {
      // error! should not pass null or undefined.
    }
  }
}

let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here
// 只能从三种允许的字符中选择其一来做为参数传递,传入其它值则会产生错误

字符串字面量类型还可以用于区分函数重载

function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
  // ... code goes here ...
}
上一篇下一篇

猜你喜欢

热点阅读