天天看點

React+TS學習和使用(二)

開啟學習react+ts,本篇主要是react函數式元件必備Hook,結合TS一起了解。

一、Hooks

1、useState

App.tsx

中使用 useState 定義資料,以及修改資料的方法,并傳遞給

Comp.tsx

子元件:

const [num, setNum] = useState(0);

<Comp1 num={num} />
   
           

子元件接收:

import React from 'react'

const Comp1: React.FC = function (props) {
    return (
        <>
            <h3>{props.num}</h3>
            <button>累加</button>
        </>
    )
}
export default Comp1;
    
           

很明顯,這麼接收直接就報錯。因為TS強制要求必須指定傳參的字段及其類型,是以應當改為:

import React from 'react'

const Comp1: React.FC = function (props: {num: number}) {
    return (
        <>
            <h3>{props.num}</h3>
            <button>累加</button>
        </>
    )
}
export default Comp1;
    
           

而實際上這是TS中接口的簡化寫法,完整點應該寫為:

import React from 'react'

interface IProps {
    num: number;
}

// 使用IProps接口定義字段類型
const Comp1: React.FC<IProps> = function (props) {
    return (
        <>
            <h3>{props.num}</h3>
            <button>累加</button>
        </>
    )
}

export default Comp1;
    
           

A. 事件直接父傳子使用

目前

setNum

依然處于定義了但未使用的狀态,是以ESlint又會一直給出提示,是以我們可以把這個累加的效果實作:

// 父元件:
<Comp1 num={num} setNum={setNum} />

// 子元件
interface IProps {
    num: number;
    // 設定setNum為any
    setNum: any
}

<button onClick={()=>props.setNum(props.num+1)}>累加</button>
    
           

注意:

這裡雖然設定為any可以實作累加,但不建議這麼操作。

是以,真正的做法:

import React from 'react'

interface IProps {
    num: number;
    setNum: (num:number)=>void;
}

// 使用IProps接口定義字段類型
const Comp1: React.FC<IProps> = function(props) {
    return (
        <>
            <h3>{props.num}</h3>
            <button onClick={()=>props.setNum(props.num+1)}>累加</button>
        </>
    )
}

export default Comp1;
    
           

B. 事件用子傳父的做法

* 父元件
import React, {useState, useCallback} from 'react'
import Comp1 from 'components/Comp1'

const App: React.FC = () => {
    const [num, setNum] = useState(0)

	const toSetNum = (value: number) => setNum(value)

    return (
        <>
            <h2>你好世界</h2>
            <Comp1 num={num} toSetNum={()=>toSetNum} />
        </>
    )
}

export default App;
    
           

setNum(newValue):代表直接用新值替換初始值

setNum(preValue => newValue):代表用新值替換舊值

* 子元件
import React from 'react'

interface IProps {
    num: number;
    toSetNum: (num:number)=>void;
}

// 使用IProps接口定義字段類型
const Comp1: React.FC<IProps> = function(props) {
    return (
        <>
            <h3>{props.num}</h3>
            <button onClick={()=>props.toSetNum(props.num+1)}>累加</button>
        </>
    )
}

export default Comp1;
    
           

2、useEffect

React的Class Component中有

componentDidMount

componentDidUpdate

componentWillUnmount

,但Function Component并沒有。

A、componentDidMount

useEffect(()=>{
  console.log('componentDidMount')
}, [])	// 空數組表示不檢測任何資料變化

    
           

B、comopnentDidUpdate

useEffect(()=>{
  console.log('comopnentDidUpdate')
}, [num])	// 如果數組中包含了所有頁面存在的字段,也可以直接不寫
   
           

如果監聽路由的變化:

// 需要先安裝路由,而且是[email protected]
useEffect(()=>{
  console.log('路由變化')
}, [location.pathname])

    
           

C、componentWillUnmount

useEffect(()=>{
  return ()=>{
    // callback中的return代表元件銷毀時觸發的事件
  }
}, [])
    
           

3、memo、useMemo與useCallback

在Function Component中,也不再區分

mount

update

兩個狀态,這意味着函數元件的每一次調用都會執行内部的所有邏輯,就帶來了非常大的性能損耗。

useMemo

useCallback

都是解決上述性能問題的。

來看下面這段代碼:

import React, { useState, useMemo, useCallback } from "react";

const Sub = () => {
  console.log("Sub被渲染了");	// 這行代碼在父元件App2更新時,它也被迫一直更新
  return <h3>Sub元件</h3>;
};

export default function App2() {
  const [num, setNum] = useState<number>(0);

  const changeNum = () => setNum(num + 1)

  return (
    <div>
      <h2>num的值:{num}</h2>
      <button onClick={changeNum}>累加num</button>
      <Sub />
    </div>
  );
}

           

以上代碼中可以測試出來,Sub元件的

console.log

在App2元件更新時,一直被迫觸發,這就是典型的性能浪費。

A. memo

使用memo這個hook可以解決這一問題:

import React, { useState, memo } from "react";

// Sub元件需要被memo包裹
const Sub = memo(() => {
    console.log("Sub被渲染了");
    return <h3>Sub元件</h3>;
  });

export default function App2() {
  const [num, setNum] = useState<number>(0);
    
  const changeNum = () => setNum(num + 1)

  return (
    <div>
      <h2>num的值:{num}</h2>
      <button onClick={changeNum}>累加num</button>
      <Sub />
    </div>
  );
}
    
           
memo可以緩存元件,當元件的内容不受修改時,可以不更新該元件。

B. useCallback

但我們希望num的變化不造成Sub元件的更新:

import React, { useState, memo, useCallback } from "react";

interface ISubProps {
  changeNum: () => void;
}

// Sub元件需要被memo包裹
const Sub = memo((props: ISubProps) => {
  console.log("Sub被渲染了");
  return (
    <>
      <button onClick={props.changeNum}>累加num</button>
      <h3>Sub元件</h3>
    </>
  );
});

export default function App2() {
  const [num, setNum] = useState<number>(0);

  // 将這個changeNum函數使用useCallback包裹一次
  const changeNum = useCallback(()=>{
      setNum((num)=>num+1)
  }, [])

  return (
    <div>
      <h2>num的值:{num}</h2>
      <Sub changeNum={changeNum} />
    </div>
  );
}

           

C. useMemo

useMemo與useCallback大緻相同,隻是useMemo需要在回調函數中再傳回一個函數,我們稱之為高階函數:

import React, { useState, memo, useMemo } from "react";

interface ISubProps {
  changeNum: () => void;
}

// Sub元件需要被memo包裹
const Sub = memo((props: ISubProps) => {
  console.log("Sub被渲染了");
  return (
    <>
      <button onClick={props.changeNum}>累加num</button>
      <h3>Sub元件</h3>
    </>
  );
});

export default function App2() {
  const [num, setNum] = useState<number>(0);

  // 将這個changeNum函數改為useMemo
  const changeNum = useMemo(() => {
    return () => setNum((num) => num + 1);
  }, []);

  return (
    <div>
      <h2>num的值:{num}</h2>
      <Sub changeNum={changeNum} />
    </div>
  );
}
  
           

4、自定義hook

React中的hook允許我們自定義,來嘗試一個簡單的:

自定義一個hook,将所有的小寫字母改大寫。
import React from 'react'

const word = "Hello World";

function useBigWord(w: string){
    return w.toUpperCase();
}

const App3 = () => {
    const bigWord = useBigWord(word)
    return (
        <div>
            <h3>小寫:{word}</h3>
            <h3>大寫:{bigWord}</h3>
        </div>
    )
}

export default App3
           

繼續閱讀