使用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中引入后,在全局引用时抱错:

下面global-lib.ts文件,如果需要全局引用,需要编写TS的声明文件[貌似是因为js文件不是用ts编写的,也就是没有对其状态做限制]
 
function globalLib(options){
  console.log(options);
}
globalLib.version = '1.0.0';
globalLib.doSomeing = function(){
  console.log('globalLib do something');
}

 

在该js文件并列一个TS文件:global-lib.d.ts
 
declare function globalLib(options:globalLib.Options):void;
declare namespace globalLib{
  const version:string;
  function doSomething():void;
  interface Options { //interface接口,对对象的形状进行描述;对类class进行抽象
    [key:string]:any
  }
}
 
某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void:
function warnUser(): void {
alert("This is my warning message");
}
注意:声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined和null
let unusable: void = undefined;
 

 2.使用模块,也就是module.exports = xxx 的形式

假设文件为: module-lib.js
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声明文件,这里会抱错
新增声明文件 .d.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/
    }
  ]
}
所以需要额外的使用插件:

npm i fork-ts-checker-webpack-plugin -D
 
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提供解析器
  }
}
新建文件:.eslintrc.json 文件
{
  "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" //可以在这里关闭具体的某个规则
  }
}
然后在 package.json 中配置 scripts 脚本:
{
  "scripts":{
    "lint":"eslint src --ext .js,.ts"//检查以js和ts为后缀的文件
  }
}
然后vscode安装eslint插件,可以右击打开eslint的配置项,从而完成自动修复功能;

1.babel-eslint: 支持TS没有的额外语法检查,抛弃TS,不支持类型检查
2.typescript-eslint: 基于TS的AST,支持创建基于类型信息的规则:tsconfig.json

建议:
两者底层机制不一样,不要一起使用
babel体系建议用 babel-eslint ; 否则可以使用 typescript-eslint

 

配置react环境:

npm i react react-dom -S
npm i @types/react @types/react-dom -D
修改ts的配置文件tsconfig.json:
{
"jsx":"react"//意思是把jsx转成js文件
/*一共有三个值:
1. preserve:生成的代码会保留jsx格式,文件的扩展名就是jsx,可以方便后续
使用,比如传递给babel
2. react-native:生成的文件是jsx格式,但是扩展名是js
3. react:转成纯js语法的文件
*/
}

 

对于Redux,没有特别的,需要注意一下类型声明即可

 

  import { Dispatch } from 'redux'
const mapStateToProps = (state:any)=>{
  comploy:state.data.list
};
const mapDIspatchToProps = (dispatch:Dispatch)=> {
  onGetList:getEmployee
}

 

posted @ 2020-04-04 17:30  小猪冒泡  阅读(1476)  评论(0编辑  收藏  举报