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() {}, };
- 还可以表示对行为的抽象,而具体如何行动需要由类(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);
- 构造函数类型的函数类型
- 类的实例类型
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 }); }} > 点击 </> ); } }