一文带你了解TypeScript基础

899 阅读18分钟

一文带你了解TypeScript基础

大家好呀~ 我是 _像个男孩❤ 答应你们zi ji的ts基础,今天他来了!之前文章的废话好像都太多了,所以这次直接上才艺!知识大纲如图所示:

1.初识TypeScript


TypeScript(TS) 是一种由微软开发的自由和开源的编程语言,它是 JavaScript 的一个超集,扩展了 JavaScript 的语法。

这是官方给的解释,不管他是不是一门全新的语言,重点是我们为什么要学习他,他有什么要我们学习的地方,我认为体现在两个方面:

  • 类型系统: 这是ts的一个核心
  • js新语法: 例如类的泛型、装饰器等

1.1 为什么学习ts

  1. 他是当下前端中最热门的开发语言之一,也是面试加分项,不知道ts的出门都不好意思跟别人say hello
  2. 开发程序更安全,在编译阶段就能避免大部分的编码错误,减少代码在正式环境下出错的可能性
  3. 编码在类型约束检测下变得更规范
  4. 各种流行框架都提供了对ts的支持: 例如AngularVueReact

1.2 ts环境搭建

ts编写的程序并不能直接通过浏览器运行,我们需要通过编译器把他先编译成js代码,他的编译器是基于 Node.js 的,所以我们需要先安装 Node.js

Node.js 安装

nodejs.org/

通过 NPM 包管理工具安装 TS 编译器

www.npmjs.com/ www.typescriptlang.org/

npm install -g typescript

TS 编译器安装成功以后,会提供一个 tsc 的命令,用于编译我们的 TS 代码文件

tsc -v

用vue构建ts新项目请欣赏大佬的这篇文章

2. ts类型初探


在学习ts基础类型之前,让我们先来看下类型系统:

类型系统
  1. 类型标注(签名)就是给数据(变量、函数、类等)添加类型说明,语法:let 变量: 数据类型;
  2. 类型检测,有了类型标注,编译器会在编译过程中根据标注的类型进行检测,使数据的使用更安全,帮助我们减少错误(注意:类型系统检测的是类型,而不是具体值)

2.1 Boolean

let isDone: boolean = false;

2.2 Number

ts 支持二进制、八进制、十进制和十六进制字面量

let age: number = 6;

2.3 String

let name: string = `dudu`;
let info: string = `Hello, my name is ${ name }, I am ${ age } years old.`;

2.4 Array

let listnumber[] = [123];
// 数组泛型  Array<元素类型>
let listArray<number> = [123];

2.5 Tuple

元组类似数组,存储的元素类型不必相同,但是需要注意:

  • 初始化数据的个数以及对应位置标注类型必须一致
  • 越界数据必须是元组标注中的类型之一(标注越界数据可以不用对应顺序 - 联合类型)
let x: [string, number];
x = ['hello'10]; // OK
x = [10'hello']; // Error

2.6 Enum

枚举的作用组织收集一组关联数据的方式,语法: enum 枚举名称 { key1=value, key2=value2 }

  • key 不能是数字
  • value 可以是数字,称为数字类型枚举,也可以是字符串,称为字符串类型枚举,但不能是其它值,默认为数字0
  • 第一个枚举值或者前一个枚举值为数字时,可以省略赋值,值为 前一个数字值 + 1
enum Color {
  Red = 1, 
  Green = 2, 
  Blue = 4
}
let c: Color = Color.Green;

2.7 Any

有的时候,我们并不确定这个值到底是什么类型或者不需要对该值进行类型检测,就可以标注为 any 类型,语法: 变量: any;,(注意:标注为 any 类型,意味着放弃对该值的类型检测,同时放弃 IDE 的智能提示)

  • 任何值都可以赋值给 any 类型
  • any 类型也可以赋值给任意类型
  • any 类型有任意属性和方法
let list: any[] = [1, true, "free"];
list[1] = 100;

未知类型unknow,属于安全版的 any,但是与 any 不同的是:

  • unknow 仅能赋值给 unknow、any
  • unknow 没有任何属性和方法

2.8 Void

表示没有任何数据的类型,通常用于标注无返回值函数的返回值类型

// 声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined和null:
let unusablevoid = undefined;
// 当一个函数没有返回值时,你通常会见到其返回值类型是 void
function warnUser(): void {
    // 没有 return
}

2.9 Null 和 Undefined

ts里,undefined 和 null 两者各自有自己的类型分别叫做 undefined 和 null。 和 void 相似,它们的本身的类型用处不是很大:

let u: undefined = undefined;
let n: null = null;

2.10 Never

当一个函数永远不可能执行 return 的时候,返回的就是 never ,与 void 不同,void是执行了 return, 只是没有值,never 是不会执行 return,比如抛出错误,导致函数终止执行

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}
  • never 类型是所有其它类型的子类
  • 其它不能赋值给 never 类型,即使是 any

3. ts类型深入


3.1 联合类型

联合类型也可以称为多选类型,当我们希望标注一个变量为多个类型之一时可以选择联合类型标注,语法: 变量: 类型一 | 类型二

function css(ele: Element, attr: string, value: string|number) {
    // 做了一些事
}

let box = document.querySelector('.box');
// ts 会提示有 null 的可能性,加上判断更严谨
if (box) {
    css(box, 'width''100px');
    css(box, 'opacity'1);
    css(box, 'opacity', [1,2]);  // Error
}

3.2 交叉类型

交叉类型也可以称为合并类型,可以把多种类型合并到一起成为一种新的类型,并且 的关系,语法:变量: 类型一 & 类型二

interface object1 {
  xnumber, 
  ystring
};
interface object2 {
  znumber
};
let new_object: object1 & object2 = Object.assign({}, {x:1,y:'2'}, {z100});

3.3 字面量类型

有时候,我们希望标注的不是某个类型,而是一个固定值,就可以使用字面量类型,配合联合类型会更有用

function setPosition(ele: Element, direction: 'left' | 'top' | 'right' | 'bottom') {
   // 做了一些事
}

box && setDirection(box'bottom');
box && setDirection(box'hehe');  // Error

3.4 类型别名

有时候类型标注比较复杂,这个时候我们可以给类型标注起一个相对简单的名字,语法: type 新的类型名称 = 类型

type path = 'left' | 'top' | 'right' | 'bottom';
function setPosition(ele: Element, direction: path) {
   // ...
}

3.5 类型推导

每次都标注类型会比较麻烦,所以我们在以下情况可以使用更加方便的特性:类型推导

  • 初始化变量
  • 设置函数默认参数值
  • 返回函数值
// ts编译器会根据当前上下文自动的推导出 value 为 number
let value = 1;
// 不能将其他类型分配给类型 number
value = '我是字符串' // Error

3.6 类型断言

有时候,我们可能标注一个更加精确的类型(缩小类型标注范围),比如:

let img = document.querySelector('#img');

这时候img的类型是Element,但是Element 类型其实只是元素类型的通用类型,如果我们去访问 src 这个属性是有问题的,我们需要把它的类型标注得更为精确:HTMLImageElement 类型,这个时候,我们就可以使用类型断言,它类似于一种 类型转换: (注意:断言只是一种预判,只是类似转换,但并非真的转换了)

// 尖括号格式
let img = <HTMLImageElement>document.querySelector('#img');
// as 格式
let img = document.querySelector('#img'as HTMLImageElement;

3.7 类型操作符

typeof

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

function padLeft(value: string, padding: string | number) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    } else {
        return padding + value;
    }
}
keyof

获取类型的所对应的类型的 key 的集合,返回值是 key 的联合类型,keyof操作的是类型

interface Person {
   name: string;
   age: number;
};
type personKeys = keyof Person;
// type personKeys = "name" | "age"
in

对值和类型都可以使用,对值进行操作时,用来判断值中是否包含指定的key(注意: in 后面的类型值必须是 string 或者 number 或者 symbol)

interface Person {
    name: string;
    age: number;
}
type newPerson = {
   [k in keyof Person]: number;
}
/**
type newPerson = {
    name: number;
    age: number;
}
*/
extends

类型继承操作符

// 示例1:
interface type1 {
    xnumber;
    ynumber;
}
interface type2 extends type1 {}
// 示例2:
type type1 = {
    xnumber;
    ynumber;
}
function fn<T extends type1>(args: T) {}
fn({x:1y2});

3.8 类型保护

有时候,值的类型并不唯一,比如联合类型的参数,这时候,在该参数使用过程中只能调用联合类型都有的属性和方法,例如:

function toUpperCase(arg: string|string[]) {
   arg.length; 
   arg.toUpperCase(1); // Error
  
   // Error 即使作为条件判断也不行
   if (arg.substring) {
        arg.substring(1);
    }
}

可以使用类型断言

if ((<string>arg).substring) {
  (<string>arg).substring(1);
}

这样还是有些麻烦,我们可以使用上面所说的typeof

if (typeof arg === 'string') {
    arg.substring(1);
} else {
   arg.push('1');
}

tsinstanceof 也是类型保护的,针对细化的对象类型判断可以使用它来处理

if (arg instanceof Array) {
  arg.push('1');
}

细心的小朋友已经知道上面我们介绍了两种类型保护(怎么有种我面试小学数学he he he he的感觉),然而有的时候,判断并不是基于数据类型或者构造函数来完成的,那么就可以自定义类型保护

function isNumber(x: any): x is number {
    return typeof x === "number";
}

x is number 是一种类型谓词,语法: xx is type 返回这种类型的函数就可以被 ts 识别为类型保护

4. ts接口


4.1 接口初探

ts主要的一个核心就是对数据结构进行类型检查,除了之前说到的一些类型标注,针对对象类型的数据,我们还可以通过接口来进行标注

// 接口: 对复杂的对象类型进行标注的一种方式
interface Point {
    xnumber;
    ynumber;
}
// 我们可以通过这个接口来给一个数据进行类型标注
let p1Point = {
    x100,
    y100
};

(注意: interface 是一个类型,不能直接作为值使用)

4.2 可选属性

接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。这时可以通过?来标注,表示该属性是可选的

interface Point {
    x?: number;
    y?: number;
}

4.3 只读属性

通过 readonly 来标注属性为只读,只读属性除了初始化以外,是不能被再次赋值的

interface Point {
    readonly xnumber;
    readonly ynumber;
}

4.4 任意属性

有时候,我们希望给一个接口添加任意属性,可以通过索引类型来实现,索引签名参数类型必须为 stringnumber 之一,两者可同时出现,同时存在时,数字索引的返回值必须是字符串索引返回值类型的子类型

interface Point {
    [prop1: string]: string;
    [prop2: number]: string;
}

5. ts类


5.1 类的属性和方法

简单说,是用来创造对象的东西,描述了所创建对象的共同属性和方法,类可以让你的系统更加可预测

// 我们声明一个 Greeter 类
class Greeter {
    // 静态属性
    greetingstring;
    // 构造函数  执行初始化操作
    constructor(message: string) {
        this.greeting = message;
    }
    // 成员方法
    greet() {
        return "Hello, " + this.greeting;
    }
}

let greeter = new Greeter("world");

5.2 类的继承

基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类,简单来说,继承父亲(类或接口)的功能,还可以增加新功能

class Animal {
    namestring;
    constructor(theName: string) { this.name = theName; }
    move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}
// 之前我们说过通过 extends 关键字继承
class Snake extends Animal {
    // 派生类包含了一个构造函数,在构造器里访问 this 的属性之前,必须调用 super(),它会执行基类的构造函数
    // 这个是 ts 强制执行的一条重要规则 !!!
    constructor(name: string) { super(name); }
    move(distanceInMeters = 5) {
        console.log("Slithering...");
        super.move(distanceInMeters);
    }
}

let sam = new Snake("Sammy the Python");
sam.move(10);
// Slithering...
// Sammy the Python moved 10m.

5.3 修饰符

public(公共)

ts中成员都默认成 public

private(私有)

当成员被标记成 private 时,它就不能在类的外部访问

class Animal {
    private namestring;
    constructor(theName: string) { this.name = theName; }
}

new Animal("Cat").name// Error: 'name' 是私有的
protected(保护)

protected 修饰符与 private 修饰符的行为很相似,但有一点不同,它在派生类中仍然可以访问

class Animal {
  protected namestring; 
  constructor(name: string) {
    this.name = name;
  }
}
class Man extends Animal {
  private lovestring;
  constructor(name: string, love: string) {
    super(name);
    this.love = love;
  }
  public say() {
    // 如果 Animal 中用 private 修饰 name 则不能访问到 name 属性
    console.log(`my name is ${this.name}, and my love is ${this.love}`);
  }
}
let me = new Man('funlee''TS');

5.4 存取器

ts支持通过getters/setters来截取对对象成员的访问,和我们之前使用方式一样,这里不过多描述

let passcode = "secret passcode";

class Employee {
    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }
    // 把对 fullName 的直接访问改成了可以检查密码的 set 方法
    set fullName(newName: string) {
        if (passcode && passcode == "secret passcode") {
            this._fullName = newName;
        }
        else {
         // 当密码不对时,会提示我们没有权限
            console.log("Error: Unauthorized update of employee!");
        }
    }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    alert(employee.fullName);
}

6. ts函数


6.1 函数类型

函数类型分为 参数类型返回值类型

// => number 为返回值类型
let myAdd(baseValue: number, increment: number) => number =
    function(x: number, y: number): number { return x + y; };

6.2 参数类型

可选参数

在参数名旁使用 ? 实现可选参数的功能,可选参数必须跟在必要参数后面

// lastName 就是可选参数
function buildName(firstName: string, lastName?: string{
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}
默认参数

可以为参数提供一个默认值,当没有传递数值或者传 undefined 时,参数有默认初始值

function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}
// "Bob Smith"
let result1 = buildName("Bob");
let result2 = buildName("Bob", undefined)
剩余参数

对于必要参数、可选参数和默认参数来说,他们都特指某一个参数,但有时想操作多个参数或有未知参数时,可以使用 arguments 来访问所有传入的参数

function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName("Joseph""Samuel""Lucas""MacKinzie");

6.3 函数重载

函数重载这个概念在一些强类型语言中才有,方法是为同一个函数提供多个函数类型定义来进行函数重载,关于函数重载,必须要把精确的定义放在前面,函数重载只是多个函数的声明,他并不会真的将你的多个重名 function 的函数体进行合并

// 函数声明
function add (arg1stringarg2string): string
function add (arg1numberarg2number): number

// 函数实现
function add (arg1string | numberarg2string | number) {
  // 必须判断两个参数的类型是否相等
  if (typeof arg1 === 'string' && typeof arg2 === 'string') {
    return arg1 + arg2
  } else if (typeof arg1 === 'number' && typeof arg2 === 'number') {
    return arg1 + arg2
  }
}

7. ts泛型


泛型 字面意思适用于多个类型,可以用来创建可重用的组件

7.1 泛型变量

// 可以把泛型变量 T 当做类型的一部分使用,而不是整个类型
function identity<T>(arg: T): T {
    return arg;
}

7.2 泛型类型

// 一个泛型接口的例子,这块需要多多尝试
interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

7.3 泛型类

// 泛型类和泛型接口差不多
class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

7.4 泛型约束

泛型函数被定义了约束就不再适用于任意类型

// 定义一个接口来描述约束条件
interface Lengthwise {
    lengthnumber;
}
// 用 接口 和 extends 来实现约束
function myfn<T extends Lengthwise>(arg: T): T {
    console.log(arg.length); 
    return arg;
}
myfn(3); // Error
myfn("abc"// 3

后记: 唉呀妈呀,对于一个小菜鸡来说,写这个真的是耗时又耗力,花了三天时间又把 ts 抓了一遍,很多例子来自官网,多多少少还是有一些收获的,所以我还是要多多多多写呀,除了自己学习,我也希望小白们可以从中学到东西,那本菜鸡真的在线欣慰了,噢!对了!推荐大佬写的TypeScript教程!那是真的好!!你们快去看呀~ 还有别忘了给孩子点点赞,多点鼓励多些爱