使用react搭建组件库(一):TypeScript知识梳理
1. 网站示例:http://vikingship.xyz/?path=/story/%E6%AC%A2%E8%BF%8E%E6%9D%A5%E5%88%B0%E8%AF%BE%E7%A8%8B--welcome
2. npm地址:https://www.npmjs.com/package/vikingship
======
1. 动态类型语言VS静态类型语言
动态类型语言:执行时才去数据类型的检查,一个变量可以是字符串,又可以改成数字【JS即是动态类型语言】,于是编写一些检查器,如eslint。
静态类型语言:在编译时就去执行数据类型检查;
node版本:12.5.0
npm版本:6.9.0
安装 typescript : npm install -g typescript@3.7.2
TS版本号: 3.7.2
每次改动ts文件,需要 tsc -w 监听变化后 用node执行,需要两步骤,所以使用 ts-node 合并了这两个方法;
https://www.npmjs.com/package/ts-node
安装之后,执行 ts-node a.ts 即可实时监听文件变化并用node执行
let isDone: boolean = false let age: number = 20 let binaryNumber: number = 0b1111 let firstName: string = 'viking' let message: string = `Hello, ${firstName}, age is ${age}` let u: undefined = undefined let n: null = null //undefined 和 null 是所有类型中的子类型,在这里定义的是number类型,但是也可以赋值给 undefined 和 null let num: number = undefined let notSure: any = 4 notSure = 'maybe it is a string' notSure = true notSure.myName notSure.getName() //联合类型,两个类型都行 let numberOrString: number | string = 234 numberOrString = 'abc' // 数组 let arrOfNumbers: number[] = [1, 2, 3, 4] arrOfNumbers.push(5)//因为定义了类型是 数字的数组类型 所以可以推入5 arrOfNumbers.push('str')//则报错 function test() { console.log(arguments) } //元组,可以定义数组包含多个类型,多一项和少一项,以及顺序不对都不行 let user: [string, number] = ['viking', 1]
interface:
/* interface接口,对对象的形状进行描述;对类class进行抽象 */ interface Person { readonly id: number;//只读属性,只有在创建的时候可以赋值 name: string;//用的是分号 age?: number;//?表示可选属性 } let viking: Person = { id: 1234, name: 'viking', } /* viking.id = 111;则报错 类似于const const用在变量上,readonly用在属性上; */
函数:
// 函数声明 // 可选函数z,相当于es6中的默认参数,function add(z:number=100){...} function add(x: number, y: number, z?: number): number {//z可有可无,可选参数必须放在最后面 if (typeof z === 'number') { return x + y + z } else { return x + y } } let result = add(2, 3, 5) //函数表达式 const add1 = function(x: number, y: number, z: number = 10): number { if (typeof z === 'number') { return x + y + z } else { return x + y } } const add3:number = add1;//这样会报错。因为add1也有类型 const add2: (x: number, y: number, z?: number) => number = add1//=>箭头不是es6中的箭头函数,而是ts中函数返回值类型
类:
类有三大特征: 封装、继承、多态
多态指的是 子类继承父类 实例化同一个函数方法为不同的方法,比如猫和狗两个子类都继承了动物类,但是他们实现的eat的方法不同
class Animal { name: string; static categoies: string[] = ['mammal', 'bird'] static isAnimal(a) { return a instanceof Animal } constructor(name: string) { this.name = name } run() { return `${this.name} is running` } } console.log(Animal.categoies) const snake = new Animal('lily') console.log(Animal.isAnimal(snake)) //类的继承 class Dog extends Animal { bark() { return `${this.name} is barking` } } const xiaobao = new Dog('xiaobao') //类的多态,继承的子类,重写父类中的方法 class Cat extends Animal { constructor(name) { super(name) console.log(this.name) } run() { return 'Meow, ' + super.run() } } const maomao = new Cat('maomao') /* 使用 public、private、protected 使用 public 外部可以访问到,private子类也无法访问,protected其子类可以访问到 readonly--设置属性只可以读,无法改动 */ class Animal2 { readonly name: string;//这样设置属性,只可以读,无法改动 constructor(name: string) { this.name = name } run() { return `${this.name} is running` } } const snake2 = new Animal2('lily'); snake2.name = 'lili';//无法改动 /* 静态属性,不用实例化,可以在class上直接访问,因为它于其他属性方法没有关系 */ class Animal3 { name: string; static categoies: string[] = ['mammal', 'bird'] static isAnimal(a) {//静态方法 return a instanceof Animal } constructor(name: string) { this.name = name } run() { return `${this.name} is running` } } console.log(Animal3.categoies);//访问类的静态属性,不用实例化类,直接就可以访问 /* interface——对对象和类的行为进行抽象定义,比如两个类Car2、 Cellphone2都要实现一个方法switchRadio, 则可以定义一个 interface,规定好 switchRadio 方法的类型,然后 implements 后 就必须要实现才行 */ interface Radio2 { switchRadio(): void; } class Car2 implements Radio2{ switchRadio() { } } class Cellphone2 implements Radio2 { switchRadio() { } } /* 类似的,如果要同时实现多个接口定义,比如 Cellphone 有个方法是 检查电池容量 */ interface Radio3 { switchRadio(): void; } interface Battery3 { checkBatteryStatus(); } class Cellphone3 implements Radio3, Battery3 { switchRadio() { } checkBatteryStatus() { } } /* 接口之间还有继承关系*/ interface Radio { switchRadio(): void; } interface Battery { checkBatteryStatus(); } interface RadioWithBattery extends Radio { //interface 继承了 Radio checkBatteryStatus(); } class Car implements Radio{ switchRadio() { } } class Cellphone implements RadioWithBattery {//实现的时候需要实现两个方法 switchRadio() { } checkBatteryStatus() { } }
枚举
/* 枚举,默认会被赋值给0开始的数字 */ enum Direction2 { Up, Down, Left, Right } console.log(Direction2.Up);//0 console.log(Direction2[0]);//反向映射,可以看作是一个数组来取值 /* 也可以手动赋值,则剩下未赋值的默认递增,比如下面的例子,Down就是11 */ enum Direction3 { Up=10, Down, Left, Right } //const 常量枚举,提升性能,编译后只是一个常量,剩下 Direction.up = "UP" const enum Direction { Up = 'UP', Down = 'DOWN', Left = 'LEFT', Right = 'RIGHT', } const value = 'UP' if (value === Direction.Up) { console.log('go up!') }
范型:
/* <>范型,一般定义函数,对象; 比如echo函数可以传入多个类型的参数,但是输出也要对应类型; 这样不能设置为any,因为会丧失类型检查,所以设置了<T>范型 注意T只是一个常规写法,不一定非要是T,相当于一个类型的占位符 echo定义了范型<T>,入参arg:T 返回的也是T 这样 const result = echo(true)//result就是boolean类型 const result = echo(123)//result就是123数字类型 也就是echo函数在定义的时候没有指明具体类型,但是在使用的时候 才去确定 输入类型和输出类型 */ function echo<T>(arg: T): T { return arg } const result = echo(true) //使用范型,定义多个参数类型,类型是对应的,如下进行转换 function swap<T, U>(tuple: [T, U]): [U, T] { return [tuple[1], tuple[0]] } const result2 = swap(['string', 123]) /* 如果不在范型后面增加类型限制,函数中无法获取arg.length; 所以要在入参的范型中增加[]数组的定义 但是这样只能输入数组类型,比如字符串也有长度,确无法使用该范型定义的函数 const arrs = echoWithArr('string')//报错 */ function echoWithArr<T>(arg: T[]): T[] { console.log(arg.length) return arg } const arrs = echoWithArr([1, 2, 3]) //约束范型 interface IWithLength { length: number //规定必须有length属性 } function echoWithLength<T extends IWithLength>(arg: T): T { console.log(arg.length) return arg } const str = echoWithLength('str') const obj = echoWithLength({ length: 10, width: 10}) const arr2 = echoWithLength([1, 2, 3]) //范型在类中的使用,实现一个队列类,实现两个方法 /* 如下面的例子,类没有定义TS类型,则默认是any; 如果入参的是 字符串类型,则console时,找不到其toFixed, 进而运行时报错 */ class Queue2 { private data = []; push(item) { return this.data.push(item) } pop() { return this.data.shift() } } const queue3 = new Queue2() queue3.push(1); queue3.push('str'); console.log(queue3.pop().toFixed()) //给类定义范型,规定输入和输出的类型一样 class Queue<T> { private data = []; push(item: T) { return this.data.push(item) } pop(): T { return this.data.shift() } } //在实例化类的时候,定义具体的类型 const queue = new Queue<number>() queue.push(1) console.log(queue.pop().toFixed()) const queue2 = new Queue<string>() queue2.push('str') console.log(queue2.pop().length) //interface接口也可以使用范型 interface KeyPair<T, U> { key: T; value: U; } //则在具体使用的时候,定义其类型 let kp1: KeyPair<number, string> = { key: 123, value: "str" } let kp2: KeyPair<string, number> = { key: 'test', value: 123 } let arr: number[] = [1, 2, 3] let arrTwo: Array<number> = [1, 2, 3] //函数使用范型,规定使用该范型的函数,需要保证输入输出的类型 interface IPlus<T> { (a: T, b: T) : T } function plus(a: number, b: number): number { return a + b; } function connect(a: string, b: string): string { return a + b } const a: IPlus<number> = plus const b: IPlus<string> = connect
类型断言
// type aliases 类型别名 type PlusType = (x: number, y: number) => number//在这里定义类型 function sum(x: number, y: number): number { return x + y } const sum2: PlusType = sum//在这里直接使用 type NameResolver = () => string//定义函数类型 type NameOrResolver = string | NameResolver//定义联合类型,可以输入函数和字符串 function getName(n: NameOrResolver): string { if (typeof n === 'string') { return n } else { return n() } } // type assertion 类型断言 function getLength(input: string | number) : number { //不能直接访问 input.length的属性,因为上面规定了是 string和number //的类型,所以只剩下这两个类型的公共属性 // const str = input as String//S大写,断言 // if (str.length) { // return str.length // } else { // const number = input as Number // return number.toString().length // } //下面是更简单的方法,<string>input就是断言input是字符串类型 if((<string>input).length) { return (<string>input).length } else { return input.toString().length } }
声明文件.d.ts文件
声明文件必须是以.d.ts为后缀【因为ts会解析项目中所有的ts后缀文件】
比如使用jquery('#name');编译的时候会报错,因为ts不知道jquery是个什么。
所以要新建一个声明文件:jquery.d.ts, 则所有的ts文件均可以使用这个声明文件
`declare var jQuery:(selector:string) => any`
@types 下面有很多第三方库的声明文件
====
分为三种情况:
1. 常规的js文件,然后使用cdn的方式在html中引入后,在全局引用时抱错:
function globalLib(options){ console.log(options); } globalLib.version = '1.0.0'; globalLib.doSomeing = function(){ console.log('globalLib do something'); }
declare function globalLib(options:globalLib.Options):void; declare namespace globalLib{ const version:string; function doSomething():void; interface Options { //interface接口,对对象的形状进行描述;对类class进行抽象 [key:string]:any } }
2.使用模块,也就是module.exports = xxx 的形式
const version = '1.0.0'; function doSomething(){ console.log('globalLib do something'); } function moduleLib(options){ console.log(options); } moduleLib.version = version; moduleLib.doSomething = doSomething; module.exports = moduleLib;
import moduleLib from './module-lib';//如果不加TS声明文件,这里会抱错
declare function moduleLib(options:Options):void interface Options{ [key:string]:any } declare namespace moduleLib{ const version : string, function doSomething():void } export moduleLib
import m from 'moment'; declare module 'momoent' { export function myFunction():void } m.myFunction = ()=> {}
在webpack中使用TS-loader来编译:
module:{ rules:[ { test:/\.tsx?$/i, use:[ { loader:'ts-loader', options:{ transpileOnly:true //关闭的时候可以提高构建速度,开启后会失去类型检查 } } ], exclude:/node_modules/ } ] }
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); { plugins:[ new ForkTsCheckerWebpackPlugin(); ] }
使用ESLint进行检查:
TS和ESLint的区别和联系
TS: 类型检查+语言转换+语法错误
ESLint: 代码风格+语法错误
{ "devDependencies":{ "eslint":"^5.16.0", "@typescript-eslint/eslint-plugin":"^1.13.0",//能够使eslint识别一些ts的特殊语法 "@typescript-eslint/parser":"^1.13.0"//为eslint提供解析器 } }
{ "parser":"@typescript-eslint/eslint-plugin", //规定eslint解析器 "plugins":["@typescript-eslint"], "parserOptions":{ "project":"./tsconfig.json" //ts 的类型信息 }, "extends":[ "plugin:@typescript-eslint/recommended"//规定使用eslint是官网提供的规则 ], "rules":{ "@typescript-eslint/no-inferrable-types":"off" //可以在这里关闭具体的某个规则 } }
{ "scripts":{ "lint":"eslint src --ext .js,.ts"//检查以js和ts为后缀的文件 } }
{ "jsx":"react"//意思是把jsx转成js文件 /*一共有三个值: 1. preserve:生成的代码会保留jsx格式,文件的扩展名就是jsx,可以方便后续 使用,比如传递给babel 2. react-native:生成的文件是jsx格式,但是扩展名是js 3. react:转成纯js语法的文件 */ }
对于Redux,没有特别的,需要注意一下类型声明即可
const mapStateToProps = (state:any)=>{ comploy:state.data.list }; const mapDIspatchToProps = (dispatch:Dispatch)=> { onGetList:getEmployee }