前言
超集: 在TS环境中写JS也是不会发生问题。
特点:
- 类型注释和编译时类型检测
- 基于类的面向对象编程
- 泛型
- 接口
- 声明文件
环境
安装
npm i -y
npm i -g typescript
ts编译器的命令 tsc -h
校验: tsc -v
ts初始化文件夹 tsc --init
构建工具配置
npm i webpack webpack-cli webpack-dev-server -D
(webpack需要增加拓展名称)loader安装 npm i ts-loader typescript -D
通过模板生成网站首页安装 npm i html-webpack-plugin -D
命令
--outdIR
指定输出的目录
--target ES6
指定编译的代码版本目标
--watch
监听模式下运行,文件发生改变的时候自动编译
-p
指定某个具体的配置文件
// json 配置
{
"compilerOptions": {
"target": "es5",
"watch": true,
"outDir": "./dist"
}
"include": ["./src/**/* "]
}
复制代码
强弱语言
定义
静态类型语言:在编译阶段确定所有变量的类型
动态类型语言: 在执行阶段确定所有变量的类型
数据类型的对比
类型注解
作用: 相当于强类型语言中的类型声明
语法: (变量/函数): type
枚举 enum
应用: 在程序中不容易记忆的硬编码,容易改变的常量抽取出来自定成枚举类型
枚举成员的名称成了value,反向映射原理
数字枚举
enum Role {
Reporter = 1,
Developer,
Maintainer,
}
//编译后
"use strict";
var Role;
(function (Role) {
Role[Role["Reporter"] = 1] = "Reporter";
Role[Role["Developer"] = 2] = "Developer";
Role[Role["Maintainer"] = 3] = "Maintainer";
})(Role || (Role = {}));
复制代码
字符串枚举
字符串的枚举不能进行反向映衬
enum Message {
Success = '恭喜你成功了',
Fail = '抱歉,失败了'
}
//编译后
"use strict";
var Message;
(function (Message) {
Message["Success"] = "\u606D\u559C\u4F60\u6210\u529F\u4E86";
Message["Fail"] = "\u62B1\u6B49\uFF0C\u5931\u8D25\u4E86";
})(Message || (Message = {}));
复制代码
readonly id: number
复制代码
接口
interface List {
id: number,
name: string
}
//输出的时候需要跳过类型检验可以加个<Result>
render(<Result>{
data: {}
})
//进行输入的索引
//数字索引的返回值,一定是字符串牵引的返回值的子类型,这是因为会发现类型转换
[x:number]:string
复制代码
function add6(x:number,y=0;z:number,q=1) {
return x+z+y=q
}
console.log(add6(1,undefined,3)) //5
function add7(x:number,...rest:number[]){
return x+rest.reduce((pre,cur) => pre+cur)
}
console.log(add7(1,2,3,4,5)) //15
复制代码
数据类型重组(函数重载)
function add8(...rest:number[]):number;
function add8(...rest:string[]):string;
function add8(...rest:any[]):any {
let first = rest[0];
if(typeof first == 'string') {
return rest.join('')
}
if(typeof first == 'number') {
return rest.reduce((pre,cur) => pre+cur)
}
}
console.log(add8(1,2,3)) //6
console.log(add8('a','b','c')) //abc
复制代码
类
类成员的属性都是实例属性而不是原型属性,
类成员的方法都是实例方法
- super() —父类的实例(派生类的构造函数constructor必须包含super的调用)
- private — 这个类既不能实例化也不能被继承,私有成员,只能在类的本身调用,不能被类的实例调用和子类调用
- protected — 这个类不能实例化而只能被继承,受保护成员只能在类和子类中使用
- readonly—只读属性 (不予许被修改的),需要提前声明
- public — 实例化属性
class Dog {
constructor(name:string) {
//protected constructor(name:string)构造函数也能进行声明protected,表明不能实例化只能继承
this.name = name
}
public name:string = 'dog'
run() {}
private pri() {} //设置私有成员
protected pro(){} //受保护成员
readonly legs:number = 4; //只读属性不能更改,需要初始化,跟实例属性一样
static food:string = 'bones'//类的静态成员,只能类名调用,不能用子类调用
}
let dog = new Dog('wang') //实例化
console.log(Dog.food) //静态类名调用成功
// console.log(dog.food) //静态子类调用失败
//dog.pri() //类的实例中调用不了
class Husky extends Dog {//继承
constructor (name:string,color: string) {//构造函数中初始化
super(name) //派生类的构造函数constructor必须包含super的调用
this.color = color; //this 只能在super之后运用
//this.pri() //子类调用报错
}
color: string //声明color,需要在constructor实例化后,还需要this在调用,
// constructor (name:string,public color: string)如果此处更改了成public,则color成了实例属性,那么声明的color这块则可以隐藏
}
/*修饰符
public 公有成员(默认,都是可见)
private 私有成员()
protected 受保护成员(只能在类和子类中访问,应用在构造函数中不能实例化只能继承)
readonly 只读属性(需要初始化)
static 静态成员(只能通过类名调用)
*/
复制代码
抽象类
只能被继承,不能被实例化 abstract,抽离出代码的共性,便于代码的复用和拓展,还能实现多态
//抽象类的明显标志abstract
abstract class Animal {
//在抽象中定义一个方法,实现方法的复用
eat () {
console.log('eat')
}
//不指定具体方法实现,抽象方法,明确子类有其他的实现
abstract sleep():void
}
class DD extends Animal {
constructor(name:string) {
super()
this.name = name
}
name: string
run() {}
sleep() {
console.log('dog')
}
}
let dd = new DD('wang');
dd.eat();
//实现多态
class Cat extends Animal {
sleep() {
console.log('Cat')
}
}
let cat = new Cat();
let animal:Animal[] = [dd,cat]
animal.forEach(i=> {
i.sleep(); //通过循环执行不同的方法内容
})
// 列式
class WorkFlow {
step1() {
return this;
}
step2() {
return this;
}
}
new WorkFlow().step1().step2(); //实现了方法的列式调用,这里的多态this是父类型也可以说是子类型
class Myflow extends WorkFlow {
next() {
return this;
}
}
new Myflow().next().step1().next().step2()//保证了子类和父类接口调用的连贯性
复制代码
类与接口的关系
一个接口(interface)
- 可以约束类成员多少属性以及类型
- 只能约束公有成员,如果是私有会报错
- 不能约束构造函数,只能约束类的公有成员
- 可以多个接口继承
//接口
interface Human {
//new (name:string):void 第三点所述,类实现接口会导致错误
name: string;
eat(): void
}
//类
class Asian implements Human {
constructor(name:string) {
this.name= name
}
name: string;
eat() {} //类实现接口的时候需要实现接口中声明的所有属性
slepp(){} //可自己声明一个
}
interface Man extends Human {
run():void
}
interface Child {
cry():void
}
// 接口继承可将多个接口合并成一个
interface Boy extends Man,Child {
}
let boy:Boy = {
name: '',
run() {},
eat() {},
cry() {}
}
// 接口继承类,接口把类的成员都抽象出来
class Auto {
static = 1
private static2 = 0 //此时 C 不是他的子类,故无法继承他的私有成员
}
interface AutoInterface extends Auto {
}
class C implements AutoInterface {
static = 1
}
class Bus extends Auto implements AutoInterface {
}
复制代码
泛型函数与泛型接口
定义: 不预先确定的数据类型,具体的类型在使用的时候才能确定。
- 原始 【需将函数和类支持多属性类型,灵活性强】
2. 进阶改变成能接受字符串的数组
或是
3. 改变成能接收任何类型的数据
any类型的实现会丢失一些信息,类型直接的约束关系
4. 泛型的实现
泛型实现案例
class Log<T> {
run(value:T) {
console.log(value)
return value
}
}
let log1 = new Log<number> ()
log1.run(7)
let log2 = new Log()
log2.run('1')
interface Length {
length:number
}
function log<T extends Length>(value:T):T {
console.log(value,value.length)
return value
}
log([1])
log('123')
log({length:1})
复制代码
泛型好处
- 函数和类可以轻松支持多种类型,增强程序的拓展性
- 不必写多条函数重载,增强可读性
- 灵活控制类型之间的约束
类型的保护机制
类型推断
定义:输入var i = 6
此处可以推断出i的属性类型是number类型
类型兼容性
x兼容Y:X(目标类型)= Y(源类型)
结构之间兼容:成员少的兼容成员多的
函数之间兼容:参数多的兼容参数少的
interface Point3 {
x:number;
y:number;
z:number
}
interface Point2 {
x: number;
y:number;
}
let p3d = (point:Point3) => {};
let p2d = (point:Point2) => {};
p3d = p2d //参数多的兼容参数少
p2d = p3d //是不兼容的
//返回值类型
let f =() => ({name:'A'})
let g =() => ({name:'A',location: 'B'})
f=g //成员少的兼容成员多
复制代码
类型保护
定义: TS能够在特定的区域中保证变量属于某种确定的类型,可以在此区域中放心的引用此类型的属性,或者调用此类型的方法。
enum Type{Strong,Week}
class Java {
hellowJava() {
console.log('Hellow Java')
}
}
class JavaScript {
hellowJavaScript() {
console.log('Hellow JavaScript')
}
}
function getLanguage(type:Type) {
let lang = type === Type.Strong?new Java():new JavaScript();
// instanceof 判断实例属不属于类
if( lang instanceof Java) {
lang.hellowJava()
} else {
lang.hellowJavaScript()
}
return lang;
}
getLanguage(Type.Strong);
复制代码
高级类型——交叉类型与联合类型
交叉类型:将多个类型合并成一个类型,新的类型将有所有的类型特性,适合混入的场景,这里取的是所有类型的并集。
interface D {
run():void
}
interface B {
jump():void
}
let pet:D&B = {
run() {},
jump() {}
}
复制代码
联合类型:声明的类型并不确定可以为多个类型中的一个。
let a:number | string = 'a' //也可以是 1
复制代码
高级类型——索引类型
对对象属性的查询和访问在配合泛型约束建立对象属性。
let obj = {
a: 1,
b: 2,
c: 3
}
function getValues<T,K extends keyof T> (obj: T,keyS:K[]):T[K][] {
return keyS.map(key => obj[key])
}
console.log(getValues(obj,['a','b']))
//console.log(getValues(obj,['ew','w']))
复制代码
高级类型——映射类型
旧的类型,生成新的类型。(预设)
高级类型——条件类型
使结果内容的不唯一性,增加语言的灵活性。
//分布式条件类型
// (A|B) extends U?X:Y
//解开后是 (A extends U ? X:Y) | (B extends U?X:Y)
type Diff<T,U> = T extends U ? never : T
type T4 = Diff<"a" | "b" | "c" ,"a" | "e" > //type T4 = "b" | "c"
//解构出 Diff <"a","a" | "e" > | Diff <"b","a" | "e" > | Diff <"c","a" | "e" >
// never | "b" | "c"
// "b" | "c"
//过滤掉undefined 和 null
type NotNull<T> = Diff<T,undefined | null >
type T5 = NotNull<string | number | undefined |null>
//Extract
type T6 = Extract<"a" | "b" | "c" ,"a" | "e" > //a
// ReturnType 返回值类型
type T7 = ReturnType<() => string > //string
//解构 ,此处运用了infer 表示待推断,待确定
复制代码
Es6与CommonJS(两者不可混用)
//单独导出
export let a = 1
//批量导出
let b = 2
let c = 3
export {b,c}
//导出接口
export interface P {
x:number;
y:number;
}
//导出函数
export function f() {}
// 导出时起别名
function g() {}
export {g as G}
// 默认导出,无需函数名
export default function () {
console.log("I'm default")
}
// 引入外部模块,重新导出
export {str as hello} from './b'
//导入
let c1 = require('./a.node')
复制代码
命名空间
命名空间在与函数进行声明合并或者与类声明合并的时候一定要放在函数或者类定义的后面
namespace Shape {
export function square(x:number) {
return x * x ;
}
}
console.log(Shape.square(1));
复制代码
声明合并
不在同个文件中也是能够发生接口的合并功能
函数声明的顺序 函数的参数是个自变量》接口内部按顺序决定》接口之间后面的接口排在前面
interface A {
x : number //非函数状态
foo (bar:number) : number; //函数状态的重载 5
foo(bar:'a'):number; //2
}
interface B {
y: number;
foo (bar:string):string; //3
foo(bar:number[]):number[]; //4
foo(bar:'b'):number; //1
}
let a:A = {
x:1,
y:1
foo(bar:any) { //函数重载实现
return bar
}
}
复制代码
声明文件
使用非TS类库的时候,必须为这个类库编写声明一个文件,对外暴露API,可通过TypeSearch查询是否有相关的类库文件
配置tsconfig
{
“files” : [
"src/a.ts"
],
"include" : [
"src"
],
"exclude" : [
"src/lib"
]
}
复制代码
编译工具 — 从ts-loader到Bable
TypeScript与ESLint
两者在转换的时候都会相继转换成AST语法树,导致识别上出现冲突,在原有的血统上推出了typescript-eslint
工具体系
注意
- 私有private: 当成员被标记成private时,就不能再声明它的类外部访问
- 保护protecte: protected成员在派生类中任然可以访问
- 只读readonly: 只读属性必须在声明时或构造函数里被初始化
- 参数属性: 给构造函数的参数加上修饰符,能够定义并初始化一个成员属性
class features {
// 此处需要我们初始化,得一个个初始化,所以运用构造器
constructor(public id: number,public name: string) {}
}
复制代码
- 存取器: 当获取和设置属性时有额外逻辑时可以使用存取器(又称为getter,setter)
class Employee {
private firstName: String = 'Mike' ;
private lastName: String = 'James';
get fullName(): string {
return this.firstName + ' ' + this.lastName
}
set fullName(newName: string) {
console.log('你修改了用户名');
this.firstName = newName.split('')[0];
this.lastName = newName.split('')[1];
}
}
const employee = new Employee();
employee.fullName = 'Bon smit'
复制代码
实战
创建项目
安装命令 npx create-react-app 项目名称 --typescript
按需加载文件配置
类型约束的react组件开发
react 中的类组件写法 需要先继承Component组件
高阶组件与Hooks
存在问题: HelloClass组件被封装后的默认属性是传不进去高阶组件的,只能将定义的接口属性设置可选。
需要注意: 1. 类型问题 (react还没很好兼容高阶组件的检测)