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<number> = [1, 2, 3];
// 接口定义数组
interface IArray {
[index: number]: number;
}
let arr: IArray = [1, 1, 2, 3, 5];
只读数组 数组创建后不能被修改
let ro: ReadonlyArray<number> = 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 ```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); } }
<a name="imyvP"></a>
## 类型推论
如果没有指定类型, **TS** 会根据类型推论推断出一个类型.<br />如果变量定义的时候没有赋值, 默认是 any 类型
```javascript
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() {}
}
- 任意属性 ```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);
- 构造函数类型的函数类型
- 类的实例类型
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>
</>
);
}
}