使用React+TypeScript構建自己的組件庫


TypeScript基礎

數據類型

ECMAScript標准定義了8種數據類型

  1. Boolean
  2. Null
  3. Undefined
  4. Number
  5. BigInt
  6. String
  7. Symbol
  8. Object

interface接口

interface的主要作用如下:

  1. 對對象的形狀(shape)進行描述
  2. 對類(class)進行抽象
  3. Duck Typing(鴨子類型)
interface Person {
  readonly id: number;
  name: string;
  age?: number;
}

函數表達式

const add: (x: number, y: number, z: number) => number = function (x, y, z): number {
  return x + y + z;
}

面向對象的三大特性:封裝,繼承,多態

  • 封裝:當我們使用類中的某個方法時,我們無需知道類中的具體實現細節
  • 繼承:子類可以繼承父類,讓子類具有父類的屬性以及方法
  • 多態:子類可以覆蓋父類中的方法,從而讓子類和父類的實例表現出不同的特性
class Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  run() {
    console.log(`${this.name} is running`)
  }
}

class Cat extends Animal {
  constructor(name: string) {
    super(name);
    // 重寫父類的構造方法,首先需要使用super調用父類的構造函數
    console.log(this.name);
  }

  run () {
    console.log('miao~');
    // 當在子類中需要調用父類的方法時,也可以使用super來調用
    super.run();
  }
}

類中屬性或者方法的訪問修飾符

  • private: 僅僅能夠在當前類的內部訪問,在子類或者實例中都無法訪問
  • protected: 可以在當前類內部和子類中訪問,無法在實例中訪問
  • public: 默認的訪問修飾符,能夠在類,子類,實例中都可以訪問到
  • readonly: 用來修飾類中不可變的屬性
  • static: 可以直接用類名來訪問其修飾的屬性和方法

接口interface

當多個class需要都需要實現某些相同的方法時,我們可以使用interface來實現

interface Radio {
  switchRadio() :void;
}

class Car implements Radio {
  switchRadio() {}
}

class Phone implements Radio {
  switchRadio() {}
}

常量枚舉

const enum Direction {
    Up = 'UP',
    Down = 'DOWN',
    Left = 'LEFT',
    Right = 'RIGHT'
}

泛型約束

可以使用extends關鍵字來對泛型參數進行限制

interface IWithLengh {
  length: number;
}

function echoWithLength<T extends IWithLengh>(arg: T): T {
  console.log(arg.length);
  return arg;
}

const str = echoWithLength('abc');
const obj = echoWithLength({ length: 1 });
const arr = echoWithLength([1, 2])

類也可以使用泛型來約束

class Queue<T> {
  private data: T[] = [];
  push(item: T): void {
    this.data.push(item);
  }
  pop(): T {
    return this.data.pop();
  }
}
const queue = new Queue<number>();
queue.push(1);
console.log(queue.pop().toFixed(2));

const queue2 = new Queue<string>()

接口也可以使用泛型來約束

interface KeyPair<T, U> {
    key: T;
    value: U;
}

使用泛型來約束函數

interface IPlus<T> {
  (a: T, b: T): T;
}
function plus(a: number, b: number): number {
  return a + b;
}
const a: IPlus<number> = plus

類型別名

類型別名多用於聯合類型中

type NameResolver = () => string;
type NameOrResolver = string | NameResolver;
function getName(n: NameOrResolver): string {
  if (typeof n === 'string') {
    return n;
  } else {
    return n();
  }
}

類型斷言

function getLength(input: string | number): number {
  if ((<string>input).length) {
    return (<string>input).length;
  } else {
    return input.toString().length;
  }
}

React基礎

使用腳手架工具搭建項目

首先使用create-react-app搭建項目

npx create-react-app ts-with-react --typescript

簡單的函數式組件

interface IHelloProps {
  message: string;
}
const Hello: React.FC<IHelloProps> = (props) => {
  return <h2>{ props.message }</h2>
}
Hello.defaultProps = {
  message: 'Hello World'
}

React Hooks解決的問題

  • 組件很難復用狀態邏輯
  • 復雜組件難以理解,尤其是生命周期函數

useState

該hook相當於組件內的狀態

const LikeButton: React.FC = () => {
  const [like, setLike] = useState(0)
  const [status, setStatus] = useState(true)
  return (
    <>
    <button onClick={() => { setLike(like + 1)}}>
      {like}
    </button>
    <button onClick={() => { setStatus(!status) }}>
      {String(status)}
    </button>
    </>
  )
}

useEffect

該hook默認會在第一次渲染完成和每次界面更新時執行,相當於class組件中的componentDidMountcomponentDidUpdate

useEffect(() => {
  document.title = `點擊了${like}次`
})

使用useEffect的返回值清除副作用

const LikeButton: React.FC = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 })
  useEffect(() => {
    const updatePostion = (e: MouseEvent) => {
      setPosition({
        x: e.clientX,
        y: e.clientY
      })
    }
    document.addEventListener('click', updatePostion)
    return () => {
      // 會在下一次更新界面之后,重新添加此effect之前執行
      document.removeEventListener('click', updatePostion)
    }
  })

  return <p>X: {position.x}, Y: {[position.y]}</p>
}

控制useEffect執行時機,這里需要使用到useEffect的第二個參數。第二個參數是一個數組,可以填入依賴項,當這些依賴項發生變化時,才會去執行useEffect。當第二個參數為空數組,顯然這種情況是沒有依賴可以變化的,因此這種情況的useEffect僅僅會在組件加載和卸載時執行一次。

useEffect(() => {
  document.title = `點擊了${like}次`
}, [like])

自定義hook

自定義hook需要以use開頭

const useMousePosition = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 })
  useEffect(() => {
    const updatePostion = (e: MouseEvent) => {
      setPosition({
        x: e.clientX,
        y: e.clientY
      })
    }
    document.addEventListener('click', updatePostion)
    return () => {
      document.removeEventListener('click', updatePostion)
    }
  }, [])
  return position
}

使用一個自定義hook

const position = useMousePosition()

使用自定義hooks封裝一個請求公共hooks

import { useState, useEffect } from 'react'
import axios from 'axios'
const useURLLoader = (url: string, deps: any[] = []) => {
  const [data, setData] = useState<any>(null)
  const [loading, setLoading] = useState(false)
  useEffect(() => {
    setLoading(true)
    axios.get(url).then(result => {
      setData(result.data)
      setLoading(false)
    })
  }, deps)
  return data
}
export default useURLLoader

完成組件庫

完成一個組件庫需要考慮的問題

  • 代碼結構
  • 樣式解決方案
  • 組件需求分析和編碼
  • 組件測試用例分析和編碼
  • 代碼打包輸出和發布
  • CI/CD,文檔生成等

CSS解決方案

  • inline css
  • css in js
  • styled component
  • sass/less

未完待續....2020年06月15日19:18:23


免責聲明!

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



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