React + TS

若是有任何錯誤,請務必指出。
參考連接:https://github.com/typescript...react

(一)構建基礎的組件文件

  1. 首先咱們須要導入 reactimport * as React from 'react'
  2. 爲每一個文件建立一個類組件。在一個文件內請儘可能只寫一個類組件,其餘的請使用函數組件git

    // ReactTs.tsx
    class TsExample extends React.Component<any, any> {}

    使用 extends 來繼承 React.component 接口github

  3. 咱們注意到 React.Component 後面帶了 <any, any> 這個奇怪的字符串。這是 React.Component 接口的類型參數。第一個是 props 的類型,第二個是 state 的類型。
  4. 爲了給 propsstate 定義類型,咱們可使用 interface 關鍵字。咱們能夠將類型定義寫在當前的組件文件,可是因爲在當前文件中具備了 import 關鍵字,當前文件會成爲一個模塊,即沒法被全局引用。因此建議新建一個文件 index.d.ts 來儲存 interface\typetypescript

    // index.d.ts
    interface ITsExampleProps {
      name: string
    }

    注:exportimport 會將一個文件變成模塊,即裏面的變量不會被暴露到全局數組

  5. 接下來咱們爲這個組件初始化 stateapp

    // ReactTs.tsx
    class TsExample extends React.Component<ITsExampleProps, ITsExampleState> {
      public state = {
        count: 0
      }
    }

    此處建議不要 public state = {}constructor 兩種方法混用函數

  6. 爲組件添加 UI:每一個類組件都應該有一個 render 方法來進行 UI 渲染。render 方法中必須返回一個 ReactNodeReactNode 能夠是一個HTMLElement 或者一個字符串,數字工具

    // ReactTs.tsx
    class TsExample extends React.Component<ITsExampleProps, ITsExampleState> {
      public state = {
        count: 0
      }
    
      public render() {
        let { count } = this.state
        return <div styleName="wrap">這裏是 ui</div>
      }
    }
  7. 爲組件添加並調用方法,使用 stateui

    // ReactTs.tsx
    class TsExample extends React.Component<IExampleProps, IExampleState> {
      public state = {
        count: 0,
        currentCount: 0
      }
    
      public plusCount = () => {
        this.setState({
          count: ++this.state.count
        })
      }
    
      public render() {
        let { count } = this.state
        return (
          <div styleName="">
            {/* 調用state */}
            <div>{count}</div>
            {/* 調用方法  */}
            <Button onClick={this.plusCount}>增長</Button>
            <div>
              <Button
                onClick={() => {
                  this.showCurrentCount(this.state.count)
                }}
              >
                當前數量
              </Button>
            </div>
          </div>
        )
      }
    }

8) 接下來咱們將該組件做爲一個子組件放到另外一個組件中this

// example.tsx
class Example extends React.Component<IExampleProps, IExampleState> {
  public render() {
    return (
      <div styleName="example">
        <ReactTs />
      </div>
    )
  }
}
  1. 爲子組件傳入一個參數

    // example.tsx
    class Example extends React.Component<IExampleProps, IExampleState> {
        public render() {
            return (
                <div styleName='example'>
                    <ReactTs name="React-Ts" />
                </div>
            )
        }
    }

10) 在子組件中使用參數:this.props.name

// ReactTs.tsx
public render() {
        let {count} = this.state
        return (
            <div styleName='React-Ts'>
                <div>名字:{this.props.name}</div>
                <div>增長數量:{count}</div>
                <div>當前數量:{this.state.currentCount}</div>
                <div><Button onClick={this.plusCount}>增長</Button></div>
                <Button
                                        onClick={() => { this.showCurrentCount(this.state.count) }}>
                      當前數量
                                </Button>
            </div>
        )
    }

(二)使用函數式組件

  1. 通常,在一個文件內,咱們應該只寫一個類組件,其餘的子組件應該是函數式組件。
  2. 函數式組件類型可使用React.FC,使用這個類型有個好處就是,提醒你必須返回一個ReactNode

    const FuncExample: React.FC<IFunExampleProps> = (props: IFunExampleProps) => {
      return <div></div>
    }

3) 在函數式組件中,咱們沒法像在類組件中同樣使用state和生命鉤子函數,但React提供了HOOK

4) 使用 useState 來存儲函數組件的狀態。在下面的例子中,咱們使用了React.useState,傳入color的初始值,返回了一個對象,咱們使用解構,得到了color, setColor。其中 color 至關於 this.state.colorsetColor('green')至關於 this.setState({color: 'green'})

const FuncExample: React.FC<IFunExampleProps> = (props: IFunExampleProps) => {
  const [color, setColor] = React.useState('blue')

  const changeColor = () => {
    setColor('green')
  }

  return (
    <div>
      <div style={{ color }}>函數式組件</div>
      <Button type="primary" className="example-button" onClick={changeColor}>
        點擊換色{' '}
      </Button>
    </div>
  )
}
  1. 假如你想像在類組件中使用 componentWillUnmountcomponentDidMountcomponentDidUpdate,你可使用useEffectuseEffect接受一個回調函數,這個回調函數內代碼會在 componentDidMountcomponentDidUpdate時執行。回調函數的返回值應該是一個函數,這個函數會在 componentWillUnmount被執行。

    const FuncExample: React.FC<IFunExampleProps> = (props: IFunExampleProps) => {
        const [color, setColor] = React.useState('blue')
      const [fontSize, setFontsize] = React.useState('16px')
    
      const changeColor = () => {
        setColor('green')
      }
    
      React.useEffect(() => {
        let timer = setInterval(() => {
          setFontsize('100px')
        }, 10000)
    
        return () => {
          clearInterval(timer)
        }
    
      return (
          <div>
          <div style={{color,fontSize}}>函數式組件</div>
          <Button type='primary' className='example-button' onClick={changeColor}>點擊換色        </Button>
        </div>
      )
    }
  2. 假如咱們須要操做 DOM,咱們能夠經過 useRef 來獲取。text.current 就是被咱們綁定的元素。注意:不容許直接操做 DOM,除非無可奈何

    const FuncExample: React.FC<IFunExampleProps> = (props: IFunExampleProps) => {
        const [color, setColor] = React.useState('blue')
      const [fontSize, setFontsize] = React.useState('16px')
    
      const text = React.useRef<HTMLDivElement>(null)
    
      const changeColor = () => {
        setColor('green')
      }
    
      const changeBgC = () => {
        (text.current as HTMLDivElement).style.backgroundColor = '#e9e9e9'
      }
    
      React.useEffect(() => {
        let timer = setInterval(() => {
          setFontsize('100px')
        }, 10000)
    
        return () => {
          clearInterval(timer)
        }
    
      return (
          <div>
          <div style={{color,fontSize}}>函數式組件</div>
          <Button type='primary' className='example-button' onClick={changeColor}>點擊換色        </Button>
          <Button type='primary' className='example-button' onClick={changeBgC}>點擊換背景色</Button>
        </div>
      )
    }

(三) 其餘

  1. 有時候咱們須要使用到默認參數 defaultProps ,這個默認參數是從多個組件的 props 中提取出來的。這時可使用 交叉類型 & ,注意,請不要在交叉類型中使用相同的屬性,就算使用了,也請不要爲兩個同名屬性定義不一樣的基礎類型,這樣將會形成這個屬性須要同時知足兩種基礎類型。
type propsType = typeof defaultProps & {
  count: number
}

const defaultProps = {
  name: 'world'
}

const DefaultPrppsExample = (props: propsType) => {
    return (
      <div>
        {props.name}
        {props.count}
      </div>
    )
  }

  // 在另外一個組件中使用
;<DefaultPrppsExample count={1} name={'默認參數示例'} />
  1. createRefforwardRef

    在函數式組件中,咱們可使用 useRef 來獲取到 React 元素。在類組件中,咱們可使用 createRef 來獲取 React 元素。當咱們須要獲取某個組件中的元素時,使用 forwardRef 來獲取這個組件內部的元素。仍是不建議直接操縱 DOM 元素

    const RefExample = React.forwardRef((props: React.CSSProperties, ref: React.Ref<HTMLInputElement>) => {
        return <input style={{ ...props}} ref={ref} />
    })
    
    class TsExample extends React.Component<ITsExampleProps, ITsExampleState> {
      ...
      public state = {
        inputRef: React.createRef()
      }
    
        public getFocus = () => {
            if (this.state.inputRef) {
                // 注意:這裏斷言的類型應該是使用 RefObject,由於其裏面的具備 current: T | null,
                // 若是使用的是 ReactRef ,這個類型是一個聯合類型,裏面有多是 null
                ((this.state.inputRef as React.RefObject<HTMLInputElement>).current as HTMLInputElement).focus()
            }
    
        }
    
         ...
       <div styleName="example-item">
              <h3>使用 createRef 和 forwardRef</h3>
                    <RefExample color='red' ref={this.state.inputRef} />
                    <Button onClick={this.getFocus}>獲取焦點</Button>
       </div>
      ...
    }

3) React 默認會把咱們的子組件掛載到父組件上。當咱們想本身指定掛載的元素時,就須要用到 createPortal 了。

這裏寫了一個簡陋的蒙層組件(樣式未徹底實現),即便咱們是TsExample中使用了這個組件,但這個組件仍是會被掛載到一個插入 body 的元素上

注意:這裏咱們在父組件上綁定了一個點擊事件,PortalExample 被點擊時依然會冒泡進而觸發父組件的點擊事件

const PortalExample = (props: IPortarExampleProps) => {
    const [modelDOM, setModelDOM] = React.useState(document.createElement('div'))
    modelDOM.classList.add('modal')
    if (props.visible) {
        document.body.append(modelDOM)
        return ReactDOM.createPortal(
            <div styleName='modal-inner'>
                蒙層
            </div>,
            modelDOM)
    }else {
        if (modelDOM.parentNode) {
            modelDOM.parentNode.removeChild(modelDOM)
        }
        return null
    }

}

class TsExample extends React.Component<ITsExampleProps, ITsExampleState> {
  ...
  <div styleName="example-item" onClick={this.handleModalClick}>
             <h1>使用 createPortal 將子組件掛載到父組件之外的元素內</h1>
             <PortalExample visible={this.state.modalVisible} />
             <Button onClick={this.toggleModal}>切換modal</Button>
    </div>
  ...
}

(四) TS 類型基本使用

咱們在使用類型註解的時候每每會使用如下類型

  1. 基本類型包括了js的七種基本類型 string, number, boolean, undefined, null, symbol,還有 void, never
  2. 可使用 type, interface 來聲明變量的形狀

1. 聯合類型

使用 | 來創造一個聯合類型。例如:type pet = 'cat' | 'dog'

當咱們須要規定一個函數參數的類型時,又恰巧有一個已經有一個接口已經知足了,咱們可使用 keyof 來快速生成一個聯合類型

2. 類型守護

當咱們在使用聯合類型時,假如兩個子類型 A 和 B 都是接口或類等時,咱們預想的是要麼是 A,要麼是 B。但實際上,A&B 也是符合 A|B 的。

這個時候咱們須要先用 in 關鍵字來判斷是符合哪一個類型再進行操做

interface IMan {
  handsNum: number
}

interface IDog {
  tailNum: number
}

public typeGuarding = (obj: IMan | IDog) => {
    if ('handsNum' in obj) {
      ...
    } else if ('tailNum' in obj) {
      ...
    }
  }

3. 交叉類型

使用 & 來創造一個聯合類型,以前已經有示例了

4. 可選類型

可選類型其實不是一個類型,但在接口和函數參數中都有可選這個概念。

interface IPet {
    name: string
  age?: number
}

interface IMan {
  ...
}

const feedPet (pet: IPet, man?: IMan) => {
  ...
}

注意:函數的可選參數必須寫在最後面

5. 默認參數

在函數聲明中,直接爲參數賦予一個值,能夠起到默認參數的做用

interface IPetDefault {
    name: string
  age = 1
}

const feedPet (pet: IPet, man={name: '我'}) => {
  ...
}

6.enum

當咱們須要使用枚舉類型時,就須要用到enum 關鍵字。默認,第一個的值爲 0,而後遞增,能夠手動指定每個的值,以後的值也會遞增。咱們能夠經過值查key,也能夠經過key 查值

enum animal {
    烏龜,
    鱷魚,
    麻雀,
    河馬
}

animal['烏龜'] // 0
animal[0] // 烏龜

7. 類型斷言

當咱們給一個函數傳入參數時,咱們知道這個參數時符合被規定的參數類型的,但編譯器是不知道的。爲了讓編譯器能知道類型符合,咱們就須要使用類型斷言,關鍵字:as

type animalName = '烏龜' | '鱷魚' | '麻雀' | '河馬'

animal[this.state.animalName as animalName] // 0

類型斷言裏還有另外一個類型:在值的後面使用 ! ,表示這個值不爲 null

type animalNameWithNull = '烏龜' | '鱷魚' | '麻雀' | '河馬' | null

animal[(this.state.animalName as animalName)!] // 0

8. 使用類型擴展(type branding)

注:類型擴展是直譯的結果,若是你知道這種用法的名字,請修正它

TS 在檢查一個類型是否符合是,並非查找類型的名字,而是比較類型的結構。也就是說,當你爲兩種類型userIdorderId 都定義了同樣的結構:{name: string}。這時候,TS 會斷定它們是同一種類型。請看下面的例子:

interface IOrderId {
  name: string
}

interface IUserId {
  name: string
}

let userId: IUserId = {
  name: 'userId'
}
let orderId: IOrderId = userId // ok

這裏你是否會疑惑呢?兩種不一樣的類型卻能夠相互賦值。這就是 結構類型檢查 了。假如名字不一樣,類型就不一樣的就是 名稱類型檢查

那咱們如何來避免呢?第一種方法是多加一個字段

interface MyUserId {
  name: string
  type: 'user'
}

interface MyOrderId {
  name: string
  type: 'order'
}

let myUserId: MyUserId = {
  name: 'user',
  type: 'user'
}

let myOrderId: MyOrderId = myUserId // error

還有另外一種方法,那就是利用 unique symbol 和 交叉類型

type UserIdString = string & { readonly brand: unique symbol }
type OrderIdString = string & { readonly brand: unique symbol }

const getUniqueUserId = (id: string) => {
  return id as UserIdString
}

const getUniqueOrderId = (id: string) => {
  return id as OrderIdString
}

let uniqueUserId: UserIdString = getUniqueUserId('1')
let uniqueOrderId: OrderIdString = uniqueUserId // error

unique symbolsymbol 的子類型。咱們在上面用交叉類型將 {readonly brand: unique symbol} 也加入到類型中去,這樣,兩種類型就算結構同樣,它們也是不相同的

9. 函數重載

函數重載用於咱們的函數擁有不一樣的輸入或輸出時

第一個函數聲明應該是最精確的,由於編譯器是從前日後開始匹配的。函數實現應該是兼容各個函數聲明的。

這裏函數重載的意義在於,若是們直接使用第三種聲明,那麼傳入 3 個參數也是能夠接受的,可是據咱們所知, padding 是不接受 3 個參數的

// 重載
function padding(all: number)
function padding(topAndBottom: number, leftAndRight: number)
function padding(top: number, right: number, bottom: number, left: number)
// Actual implementation that is a true representation of all the cases the function body needs to handle
function padding(a: number, b?: number, c?: number, d?: number) {
  if (b === undefined && c === undefined && d === undefined) {
    b = c = d = a
  } else if (c === undefined && d === undefined) {
    c = a
    d = b
  }
  return {
    top: a,
    right: b,
    bottom: c,
    left: d
  }
}

10. 使用 typeof 關鍵字快速定義一個類型

有時候,咱們在前面定義了一個變量,並使用類型推導(也就是說,這時候咱們並無明確地給它聲明類型)。而後咱們想要使用這個變量的類型了,怎麼辦?回去定義一下類型嗎?使用 typeof 變量名 就能夠獲取到這個變量的類型了

11. 工具泛型的使用

TS 中內置了一些語法糖給咱們使用,例如 Partial、Omit、Exclude

這裏舉例 partial 的用法:Patial 關鍵字可讓將一個接口內部的屬性變成可選

interface dog {
  name: string
  bark: boolean
}

type littleDog = Partial<dog>

let dog1: littleDog = {
  name: 'dog1'
}

let dog2: littleDog = {
  bark: false
}

12. 模塊聲明

當咱們須要引入某些模塊時,這些模塊又沒有被聲明過,這個時候就會報錯了。咱們只須要使用

decleare module 模塊名 就可解決

decleare module "*.png"
import * as logo from 'logo.png'
相關文章
相關標籤/搜索