前端应该掌握的Typescript基础知识

2022-03-07 14:57:36 浏览数 (1)

TS 介绍

TS 是什么

js 是一门动态弱类型语言, 我门可以随意的给变量赋不同类型的值 ts 是拥有类型检查系统的 javascript 超集, 提供了对 es6 的支持, 可以编译成纯 javascript,运行在任何浏览器上。 TypeScript 编译工具可以运行在任何服务器和任何系统上。TypeScript 是开源的。

为什么要用 TS

ts 总体给我的感觉就是, 它能约束代码, 又有一定的灵活度, 可以培养你的编程习惯, 输出更高质量, 维护性高, 可读性高的代码

  • 编译代码时,进行严格的静态类型检查, 编译阶段而不是运行时发现很多错误, 特别是一些很低级的错误
  • 帮助我们在写代码的时候提供更丰富的语法提示, 方便的查看定义对象上的属性和方法 比如: 你给函数传了一个对象, 你在函数实现的时候还得记住对象里面都有啥参数, 你定义的参数名字是啥

TS 安装

npm init -y npm install typescript -g

编译

tsc --init tsc

数据类型

js 中的数据类型: 字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol。 对象(Object)、数组(Array)、函数(Function)

ts 包含 js 中所有的类型, 而且还新增了几种类型 void、any、never、元组、枚举、高级类型

类型注解:

(变量/函数):type

  • 布尔类型(boolean)

let flag: boolean = true

  • 数字类型(number)

let num: number = 8;

  • 字符串类型(string)

let str: string = 'sxh';

  • 数组类型(array)

let arr1: number[] = [1, 2, 3]; let arr2: Array = [1, 2, 3]; // 接口定义数组 interface IArray { [index: number]: number; } let arr: IArray = [1, 1, 2, 3, 5];

只读数组 数组创建后不能被修改

let ro: ReadonlyArray = arr1; // arr1.push(3); // ro[1] = 4; // ro.push(6); // ro.length = 100; // arr1 = ro; // let arr3: number[] = ro

  • 元组类型(tuple)

控制了数组成员的类型和数量

let tuple: [string, number] = ['sxh', 18]; // 元组越界问题: tuple.push(2) // 可以添加的类型是所有数组成员的联合类型 console.log(tuple[2]) // 不能这样访问

  • 枚举类型(enum)

普通枚举

若枚举类型未指定值或指定的值为number类型, 可对其进行双向取值

// 双向取值 enum Color { RED, BLUE, } console.log(Color[0], Color[1]); console.log(Color['RED'], Color['BLUE']); enum Color { RED=2, BLUE, } enum ActionType { ADD = 'ADD', EDIT = 'EDIT', DELETE = 'DELETE', }

常量枚举

const enum Status { 'success', 'warning', 'fail', } let loginStatus = [Status.success, Status.warning, Status.fail];

keyof typeof Enum, 将枚举类型转换为联合类型

enum ActionType { ADD, EDIT, DELETE, } type ActionTypeConst = keyof typeof ActionType // 'ADD' | 'EDIT' | 'DELETE'

  • null、undefined null, undefined 是其他类型的子类型, 可以赋值给其他类型的变量 strictNullChecks 为 true 的话不能赋值给其他类型

let str: string; str = null; str = undefined;

  • 任意类型(any) 任意类型 any 类型 类型转换困难的时候, 数据类型结构复杂,没有类型声明的时候用 如果变量定义为 any 类型, 跟 js 差不多了, 不进行类型检查了
  • unkonwn 未知类型

let a: any let b: unkonwn a.toFixed() b.toFixed()

  • void 无类型

常用于没有具体返回值的函数

const handler = (param: string):void => {}

  • never 类型

永远不存在的值

任何类型的字类型, 可以赋值给任何类型

但是任何类型都不可赋值给 never, 包括 any

function error(msg: string): never { throw new Error('我报错了'); // 直接异常结束了 } function loop(): never { while (true) {} } function fn(x: number | string) { if (typeof x === 'number') { // 类型保护 console.log(x); } else if (typeof x === 'string') { console.log(x); } else { console.log(x); } }

类型推论

如果没有指定类型, TS 会根据类型推论推断出一个类型.

如果变量定义的时候没有赋值, 默认是 any 类型

let x; // 可以赋值为任何类型的值 let x1 = '生生世世'; // x1会推论成sring类型, 不能给x1赋值为其他类型了 // x1 = 222;

如果 TS 能正确推断出其类型, 我们可采用类型推论而不必定义类型

function sum(x: number, y: number){ return x y; }

联合类型

let name1: string | number; // console.log(name1.toFixed()); // console.log(name1.toString()); name1 = 3; name1 = 'sxh';

类型断言

类型断言用来告诉编译器 “我知道自己在干什么”, 有 尖括号as 两种写法. 在 tsx 语法中, 只支持 as.

let name1: string = '111' let name2: string | number; // console.log(name2.toFixed()) console.log((name2 as number).toFixed()); // 双重断言 console.log((name1 as any) as boolean);

字符串字面量类型

字符串字面量类型用来约束取值只能是某几个字符串中的一个

“类型别名与字符串字面量类型都是使用 type 进行定义”

type Pos = 'left' | 'right'; function loc(pos: Pos) {} // loc('up')

字符串字面量 vs 联合类型

type T1 = '1' | '2' | '3'; type T2 = string | number | boolean; let t1: T1 = '2'; let t2: T2 = true;

函数

函数定义

定义函数有两种方式: 1. 函数定义 2.函数表达式

一个函数有输入和输出,要在 TypeScript 中对其进行约束,需要把输入和输出都考虑到

// 1.直接声明 function person2(name: string): void { console.log(name); } person2('sxh'); // 2.变量声明 let sum: (x:number, y:number) => number sum = (a,b)=>a b sum(1,2) // 3.类型别名 type Sum = (a: number, b: number) => void; let sum: Sum = function (a: number, b: number): number { return a b; }; let sum2: Sum = function (a: number, b: number): void {// 没有返回值 // return a b; }; const myAdd = (x: number, y: number) => x y; // 也可以直接这样定义, 类型会自动推导 // 4.接口 interface Sum{ (x:number, y: number):number } let sum: Sum = (a,b)=>a b sum(1,2)

“在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。”

可选参数

必须放在最后一个参数位置

function sum3(a: number, b: number, c?: number) {} sum3(1, 2); // 设置默认值, 最后一个参数设置默认值, 函数调用可传可不传, 相当于可选参数 function sum4(a: number, b: number, c = 6) { return a b c; } sum4(1, 2); // 第一个参数设置默认值, 使用默认调用的时候必须传 undefiend function sum5(a = 3, b: number) { return a b; } console.log(sum5(undefined, 2));

剩余参数

function sum6(...args: number[]) { return args.reduce((val, item) => val item, 0); } sum6(1, 2, 3, 5);

函数的重载

给同一个函数提供多个函数定义

let catOpt: any = {}; function cat(param: string): void; function cat(param: number): void; function cat(param: any) { if (typeof param === 'string') { catOpt.name = param; } else if (typeof param === 'number') { catOpt.age = param; } } cat('小花'); cat(3); function add(a: string, b: number): void; function add(a: number, b: number): void; function add(a: string | number, b: string | number): void {} add(1, 2); // add(1, '2');

如何定义类

class Book { name: string; getName(): void { console.log(this.name); } } let book1 = new Book(); book1.name = 'ts'; book1.getName();

存取器

通过存取器来改变一个类中属性的读取和赋值行为

class MyBook { bname: string; // 属性 constructor(bname: string) { this.bname = bname; } get name() { return this.bname; } set name(value) { this.bname = value; } } let myBook = new MyBook('ts'); myBook.name = 'js'; console.log(myBook.name);

参数属性

class MyBook1 { // bname: string; constructor(public bname: string) { // this.bname = bname; } get name() { return this.bname; } set name(value) { this.bname = value; } } let myBook1 = new MyBook1('ts'); myBook1.name = 'js'; console.log(myBook1.name);

readonly

class MyBook2 { readonly bname: string; // 公开的只读属性只能在声明时或者构造函数中赋值 readonly num: number = 1; constructor(bname: string) { this.bname = name; } changeName(value) { // this.bname = value; } }

继承

class Animal { name: string; constructor(name: string) { this.name = name; } eat(food: string) { console.log('吃什么', food); } } class Cat extends Animal { color: string; constructor(name: string, color: string) { super(name); this.color = color; } } let cat = new Cat('哈哈', 'white'); console.log(cat.name); cat.eat('fish');

类里面的修饰符

public 公有属性, private私有属性, protected受保护的

// public 公有属性 , ts默认为public class Animal1 { public name: string; // 自己, 子类和实例都可以访问 private age: number = 2; // 自己可以访问, 子类 和 实例 都不可以访问, protected body: string; // 自己和子类可以访问, 实例不可以访问 public constructor(name: string, age: number) { this.name = name; this.age = age; } public eat(food: string) { console.log('吃什么', food); } private getAge() { return this.age; } } class Dog extends Animal1 { color: string; constructor(name: string, age: number, color: string) { super(name, age); this.color = color; } dogInfo() { // console.log(this.name); // console.log(this.body); // console.log(this.age); } } let an = new Animal1('哈哈', 2); let dog = new Dog('哈哈', 2, 'white'); console.log(dog.name); // console.log(dog.age); // console.log(dog.body); // console.log(dog.getAge());

静态属性 静态方法

类自身上的属性和方法

class Button { static type = 'link'; static getType() { return Button.type; } public content: string; constructor(content: string) { this.content = content; } } console.log(Button.getType); console.log(Button.type); class SubButton extends Button {} let btn = new SubButton('ok'); SubButton.type = 'e'; console.log(Button.type); console.log(SubButton.type);

抽象类

是抽象概念, 不能被实例化

abstract class Input { label: string; abstract changeValue(): void; // 此方法在子类中必须得实现 } class SearchInput extends Input { changeValue() {} // 必须得实现抽象类里抽象方法 }

抽象方法 不包含具体实现, 必须在子类中实现 有关键字 abstract

接口

  • 接口

1.定义对象的类型,描述对象的形状

interface Cats { // 多属性和少属性都不行 name: string; text?: string; // 可选属性 speak(): void; } let Cat: Cats = { name: 'sxh', speak() {}, };

  1. 还可以表示对行为的抽象,而具体如何行动需要由类(classes)去实现(implement 同名接口可以写多个, 类型会自动合并

interface Plus { add(): void; } interface Minus { minus(): void; } class Compute implements Plus, Minus { //使用接口约束类 add() {} minus() {} }

  • 任意属性

interface Book { readonly id: number; name: string; // [key: string]: any; } let b: Book = { id: 1, name: 'sxh', // age: 18, };

  • 接口的继承

interface Book1 { getName(): void; } interface Book2 extends Book1 { getAuth(): string; } class Ts implements Book2 { // 两个接口里的方法都得实现 getName() {} getAuth() { return 'sss'; } }

  • readonly

interface Book3 { readonly id: number; } let b3: Book3 = { id: 2, }; // b3.id = 8

  • 函数类型接口 函数类型接口, 接口修饰函数

interface SumHandle { (a: number, b: number): number; } const sum11: SumHandle = (a: number, b: number): number => { return 2; }; // 修饰函数,描述函数 interface Data1 { (id: number): any; // label: string; } // 描述对象的属性name interface Data2 { name: (id: number) => any; } let e: any = () => {}; e.label = 'ts'; // let d1: Data1 = e; let d1: Data1 = () => {}; let d2: Data2 = { name() {}, };

  • 可索引接口 对数组和对象进行约束

interface Lists { [id: number]: number; } const list: Lists = [1, 3, 4]; const listObj: Lists = { 0: 2, 3: 5, };

  • 类接口

构造函数的类型

class List { constructor(public id: number) {} } // 修饰普通函数 // 如果加上 new 之后描述构造函数类型 interface Data { new (id: number): any; } // let l : Data = List // 类本身作为参数传递, 约束参数为构造函数类型 function createClass(constr: Data, id: number) { return new constr(id); } let list2: List = createClass(List, 66); console.log(list2.id);

  1. 构造函数类型的函数类型
  2. 类的实例类型

class App { static state: string = 'attr1'; state: string = 'attr2'; } // let com = App; // App类名本身表示的是实例的类型 // ts中有两个概念一个是类型, 一个是值;冒号后面的是类型, 等号后面的是值 let aa: App = new App(); let bb: typeof App = App;

结构类型系统

接口的兼容性

ts 类型的检查原则, 有一个东西看起来像鸭子、听起来像鸭子、叫起来也像鸭子,那么我们就可以认为他是鸭子 当一个类型 Y 可以被赋值给另一个类型 X 时, 就可以说类型 X 兼容类型 Y X 兼容 Y: X(目标类型) = Y(源类型)

interface bb { name: string; age: number; id: string; } interface cc { name: string; age: number; } let c1: cc = { name: 'sxh', age: 18, }; let b1: bb = { name: 'kkb', age: 11, id: 'abc111', }; function getAge(c: cc): void {} getAge(c1); getAge(b1); // b1里包含此中所有的属性就可以

基本类型的兼容性

let aa: string | number; let str: string = 'sxh'; // aa = str; // str = aa;

类的兼容性

构造函数和静态成员是不进行比较的, 只要他们有相同的实例对象, 就是相互兼容的, 如果两个类中有私有成员就不相互兼容了

class A { constructor(a: number, b: number) {} id: number = 1; // private name: string = 'ss'; } class B { constructor(a: number) {} id: number = 2; // private name: string = 'ssf'; } let a = new A(1, 2); let b = new B(3); a = b; b = a; class C extends A {} let c = new C(1, 3); c=a; a=c; // 父类和子类结构相同,相互兼容 }

函数的兼容性

1.比较参数

type Func = (a: number, b: number) => void; // 函数类型,目标类型 let sum22: Func; function fn1(a: number, b: number): void {} sum22 = fn1; // 少一个可以 function fn2(a: number): void {} sum22 = fn2; // 少两个可以 function fn3(): void {} sum22 = fn3; // 多一个不可以 function fn4(a: number, b: number, c: number): void {} // sum22 = fn4; //报错, sum22只有两个参数, 永远不可能多一个, 多传一个参数就永远不会满足条件

2.比较返回值

type g = () => { name: string; age: number }; // 这个是类型定义,不是箭头函数 let gg: g; function g1() { return { name: 'sxh', age: 11 }; } gg = g1; function g2() { return { name: 'sxh', age: 11, id: '333' }; } gg = g2; // 返回值里多一个参数可以, 其他属性可以满足就可以 function g3() { return { name: 'sxh' }; } // gg = g3;// 少一个不行

函数参数的双向协变

返回值类型是协变的,而参数类型是逆变的 返回值类型可以传子类,参数可以传父类 参数逆变父类 返回值协变子类

type Fn = (a: number | string) => number | string; function run(fn: Fn): void {} // type F = (a: string) => number; // let f: F; // run(f); // type F = (a: string) => string | number | boolean; // let f: F; // run(f); // type F = (a:number | string | boolean) => string | number | boolean; // let f: F; // run(f); type F = (a:number | string | boolean) => string ; let f: F; run(f);

Ts 中, 参数类型是双向协变的 ,也就是说既是协变又是逆变的,而这并不安全。可以通过配置 strictFunctionTypes 参数修复这个问题

枚举的兼容性

枚举类型与数字类型兼容,并且数字类型与枚举类型兼容 不同枚举类型之间是不兼容的

//数字可以赋给枚举 enum Colors {Red,Yellow} let c:Colors; c = Colors.Red; c = 1; c = '1'; //枚举值可以赋给数字 let n:number; n = 1; n = Colors.Red;

类型保护

typeof 类型保护

instanceof 类型保护

null 保护

链判断运算符

可辨识的联合类型

in 操作符

自定义的类型保护

命名空间

namespace 解决一个模块内命名冲突的问题

// 直接在文件里写的话就是全局变量, 会与其他文件相同的变量冲突 //let a = 1; // let b = a; namespace Box { // Box是全局唯一的, 里面导出的变量不能重复 export class book1 {} }

如果文件里出现了import或者export, 那么这个文件就是外部模块, 简称模块, 里面的变量都是私有变量

// 解决全局变量的问题 export let a = 1; export let b = 2; const c = 3; namespace Box { export class book1 {} }

项目环境安装

一个简单的项目初始化

npm i react react-dom @types/react @types/react-dom -S npm i webpack webpack-cli html-webpack-plugin -D npm i typescript ts-loader source-map-loader -D

创建ts配置文件, tsconfig.json

{ "compilerOptions": { "outDir": "./dist", // 输出目录 "sourceMap": true, "strict": true, "noImplicitAny": true, // 是否允许出现隐含的any类型 "jsx": "react", // 如何编译jsx语法, 生成的代码是纯的js "module": "commonjs", // 模块规范, 要把所有的模块编译成什么模块 "target": "es5", // 编译目标 "esModuleInterop": true // 允许在commonjs模块和es module进行转换 兼容其他模块的导入方式 }, "include": ["./src/**/*"] // 只编译src目录下面的ts文件 }

创建webpack.config.js文件

const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const path = require("path"); module.exports = { mode: "development", entry: "./src/index.tsx", output: { filename: "bundle.js", path: path.join(__dirname, "dist"), }, devtool: "source-map", devServer: { hot: true, contentBase: path.join(__dirname, "dist"), }, resolve: { extensions: [".ts", ".tsx", ".js", ".json"], alias: { "@": path.resolve("src"), }, }, module: { rules: [ { test: /.tsx?

在src目录中创建index.tsx、index.html文件, 编写完组件就可以启动项目了

index.tsx

import React, { Component } from 'react'; import ReactDom from 'react-dom'; import Count from './count1'; ReactDom.render(, document.getElementById('root'));

count1.tsx

import React, { Component } from 'react'; interface IProps { num: number; // name: string; } // 函数组件 // const Count = (props: Props) =>

{props.num}

; // Count.defaultProps = { // num: 10, // }; //类组件 interface State { count: number; } export default class Count extends Component<IProps, State> { state: State = { count: 0, }; static defaultProps = { num: 10, }; render() { return ( <>

点击了{this.state.count}次

<button onClick={() => { this.setState({ count: this.state.count }); }} > 点击 </> ); } }

0 人点赞