使用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
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM