開啟學習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