TS 介绍

TS 是什么

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

为什么要用 TS

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

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

TS 安装

  1. npm init -y
  2. npm install typescript -g

编译

  1. tsc --init
  2. tsc

数据类型

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

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

类型注解:
(变量/函数):type

  • 布尔类型(boolean)
  1. let flag: boolean = true
  • 数字类型(number)
  1. let num: number = 8;
  • 字符串类型(string)
  1. let str: string = 'sxh';
  • 数组类型(array)
  1. let arr1: number[] = [1, 2, 3];
  2. let arr2: Array<number> = [1, 2, 3];
  3. // 接口定义数组
  4. interface IArray {
  5. [index: number]: number;
  6. }
  7. let arr: IArray = [1, 1, 2, 3, 5];

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

  1. let ro: ReadonlyArray<number> = arr1;
  2. // arr1.push(3);
  3. // ro[1] = 4;
  4. // ro.push(6);
  5. // ro.length = 100;
  6. // arr1 = ro;
  7. // let arr3: number[] = ro
  • 元组类型(tuple)

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

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

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

  1. // 双向取值
  2. enum Color {
  3. RED,
  4. BLUE,
  5. }
  6. console.log(Color[0], Color[1]);
  7. console.log(Color['RED'], Color['BLUE']);
  8. enum Color {
  9. RED=2,
  10. BLUE,
  11. }
  12. enum ActionType {
  13. ADD = 'ADD',
  14. EDIT = 'EDIT',
  15. DELETE = 'DELETE',
  16. }

常量枚举

  1. const enum Status {
  2. 'success',
  3. 'warning',
  4. 'fail',
  5. }
  6. let loginStatus = [Status.success, Status.warning, Status.fail];

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

  1. enum ActionType {
  2. ADD,
  3. EDIT,
  4. DELETE,
  5. }
  6. type ActionTypeConst = keyof typeof ActionType // 'ADD' | 'EDIT' | 'DELETE'
  • null、undefined
    null, undefined 是其他类型的子类型, 可以赋值给其他类型的变量
    strictNullChecks 为 true 的话不能赋值给其他类型
  1. let str: string;
  2. str = null;
  3. str = undefined;
  • 任意类型(any)
    任意类型 any 类型 类型转换困难的时候, 数据类型结构复杂,没有类型声明的时候用
    如果变量定义为 any 类型, 跟 js 差不多了, 不进行类型检查了
  • unkonwn 未知类型

    1. let a: any
    2. let b: unkonwn
    3. a.toFixed()
    4. b.toFixed()
  • void 无类型

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

    1. const handler = (param: string):void => {}
  • never 类型

    永远不存在的值
    任何类型的字类型, 可以赋值给任何类型
    但是任何类型都不可赋值给 never, 包括 any ```javascript 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); } }

  1. <a name="imyvP"></a>
  2. ## 类型推论
  3. 如果没有指定类型, **TS** 会根据类型推论推断出一个类型.<br />如果变量定义的时候没有赋值, 默认是 any 类型
  4. ```javascript
  5. let x; // 可以赋值为任何类型的值
  6. let x1 = '生生世世'; // x1会推论成sring类型, 不能给x1赋值为其他类型了
  7. // 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() {}
}
  • 任意属性 ```javascript interface Book { readonly id: number; name: string; // [key: string]: any; }

let b: Book = { id: 1, name: ‘sxh’, // age: 18, };


- 接口的继承

```javascript
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
  • 函数类型接口
    函数类型接口, 接口修饰函数
    ```javascript 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() {}, };


- 可索引接口<br />对数组和对象进行约束
```javascript
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?$/,
        loader: "ts-loader",
      },
      {
        enforce: "pre",// 提前执行 
        test: /\.tsx?$/,
        loader: "source-map-loader", // 调试ts源代码
      },
    ],
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
    }),
    new webpack.HotModuleReplacementPlugin(),
  ],
};

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

import React, { Component } from 'react';
import ReactDom from 'react-dom';
import Count from './count1';

ReactDom.render(<Count num={66} />, document.getElementById('root'));

count1.tsx

import React, { Component } from 'react';
interface IProps {
  num: number;
  // name: string;
}
// 函数组件
// const Count = (props: Props) => <div>{props.num}</div>;
// 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 (
      <>
        <p>点击了{this.state.count}次</p>
        <button
          onClick={() => {
            this.setState({ count: ++this.state.count });
          }}
        >
          点击
        </button>
      </>
    );
  }
}