• 使用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,  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
    }
  • 相关阅读:
    设计模式之八:外观模式(Facade)
    Python模块学习笔记— —time与datatime
    Android加载图片OOM错误解决方式
    [C#]Attribute特性(2)——方法的特性及特性参数
    [C#]Attribute特性
    [Winform]一个简单的账户管理工具
    [C#]AES加密算法实现
    [C#基础]ref和out的区别
    [Socket网络编程]一个封锁操作被对 WSACancelBlockingCall 的调用中断。
    [Socket网络编程]由于套接字没有连接并且(当使用一个 sendto 调用发送数据报套接字时)没有提供地址,发送或接收数据的请求没有被接受。
  • 原文地址:https://www.cnblogs.com/xiaozhumaopao/p/12632793.html
Copyright © 2020-2023  润新知