1、useMemo、useCallBack
useMemo:
- const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); 返回一個memoized 值。
- 它能夠避免組件每次重新渲染時重複進行複雜的計算,參數為一個函數和可選的依賴項數組。當不傳依賴項時,隨著組件重新渲染重新計算;當傳入空數組依賴項時,只會返回首次渲染的計算結果;當有傳入的依賴項發生變化時,會重新計算結果並返回。為了達到優化效果,計算函数中引用的值都应该出现在依赖项数组裡。
- 雖然传入 useMemo 的函数会在渲染期间执行。但最好不要在這個函數內部執行與渲染無關的操作,如副作用(請求接口等)這類的操作屬於 useEffect 的適用範疇,而不是 useMemo。
useCallBack:
- const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);返回一个 memoized 回调函数。依賴項用法與useMemo類似。但需要注意的是,當依賴項數組為空時,傳入useCallback的函數的內部通过閉包取得組件內的變量值始終不變。
- useCallBack更常用於減少子組件的重新渲染,需要子組件配合React.memo或者shouldComponentUpdate使用。但如果依賴項變化頻率很高,子組件依然多次重複渲染,我會考慮用ahook的usePersistFn替換useCallBack使用。
對比:兩者都是優化渲染性能的手段方法,useMemo返回的是值而useCallBack返回的是函數,且useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
2、useState、useRef
- useState的值在每個render中都是獨立存在的,而useRef.current更像是相對於render函數中的一個全局變量,每次render都會保持最新的狀態。
- useState值更新會觸發組件重新渲染,而useRef.current不會觸發重新渲染。
- useRef.current是可變的,能保存任何值,不僅僅用於Dom引用,可當類實例去使用。
3、React.memo()
這是一個高階方法,React.memo()的返回值是一個新的函數組件。在組件中,props或state發生變化,組件就會重新渲染,如果父組件需要經常更新數據,但其實傳給子組件的props並沒有發生變化,但是子組件卻依然要更新,這樣會增加消耗。所以react.memo就是用來給子組件裹上一層,props不發生變化時(淺對比),組件不需要重新渲染。react.memo()只能用於函數組件,類似於類組件中的pureComponent、shouldComponentUpdate的功能。它可接受2个参数,第一个参数为纯函数组件,第二个参数用于对比props控制是否刷新的,与shouldComponentUpdate功能类似。
相關代碼:
Demo(涉及useState、useRef、useMemo、useCallBack,react.memo的使用)
最初的版本:
父组件: 記錄name和age兩個變量, 可點擊按鈕增加年齡。
子组件: 記錄組件本身渲染的次數,顯示父組件的信息。
a.Parent傳給Child的props並沒有發生變化,但是只要age發生變化,父組件重新渲染,子組件也會渲染,子組件用react.memo包裹就能解決這個問題。
const Child = React.memo(({ name }) => {
...略
});
AI 代码解读
b.當我在父組件中把較為複雜的數據類型傳給子組件(如下),父組件修改age後,子組件也跟著重新渲染了(如图1),是react.memo失效了嗎?其實是父组件渲染時,const info = { name, age } 一行会重新生成一个新對象,導致傳遞给子组件的 info 屬性值變化,就導致子組件重新渲染。
解決方案如下,只有当name发生变化时,才会重新计算info的结果,这样当父组件修改age时,子组件就不会再渲染了(如图2)。
c.当我给子组件一个方法能修改父组件的名字时(如下)
修改父组件age时会引起子组件重新渲染,是因为父组件每次渲染都会重新构建一个changeName方法(返回不同的引用地址),也是传给子组件的props发生了变化,所以重新渲染,但其实这是没必要的,修改方案如下:
结果:
d.如果changeName方法修改的新名字依赖age(如下),useCallBack的依賴項依然為空數組的話,這樣不管父組件的age增大到多少,子組件點擊changeName一直都是 newName 0。但是把useCallBack的依賴設置為[age]以後,這樣父組件修改age以後會引起changeName引用地址變化從而導致子組件重新渲染,這時候可以用usePersistFn替換useCallBack使用,用来解决useCallBack的依賴項變化頻率過高导致的子组件重複渲染問題。
4、自定義hook
組件由ui和邏輯組成,自定義hook可以將邏輯提取到可重用的函數中。要注意的是,與 React 組件不同的是,自定義 Hook 不需要具有特殊的標識。我們可以決定它的參數是什麽,以及它應該返回什麽。換句話說,它就像一個正常的函數,但是它的名字應該始終以 use 開頭。我之前很少寫自定義hook,但是最近有經常用到一個項目別人寫的hook代碼,哎真香,看來平時自己還是得多總結多提取,才會有進步啊。最近經常用到的自定義hook:
import { useState, useEffect, useCallback } from 'react';
import { message } from 'antd';
export default function useTablePage(service, options = {}) {
const { defaultPageNum = 1, defaultPageSize = 10, defaultQueryParam = {} } = options;
const [loading, setLoading] = useState(false);
const [params, setParams] = useState({
pageNum: defaultPageNum,
pageSize: defaultPageSize,
...defaultQueryParam,
});
const [listData, setListData] = useState([]);
const [total, setTotal] = useState(0);
const _getTableData = useCallback(() => {
setLoading(true);
service(params).then(({ success, message: errMsg, data, code }) => {
setLoading(false);
if (!success && code !== '61200130') {
message.error(errMsg);
return;
}
setListData(data?.list);
setTotal(data?.totalCount);
});
}, [service, params]);
const loadTableData = useCallback((curParams = {}) => {
setParams((prev = {}) => ({
...prev,
...curParams,
}));
}, []);
const handleFilter = useCallback(
(pagination) => {
const { current: pageNum, pageSize } = pagination;
loadTableData({
pageNum,
pageSize,
// timeSort: sorter.order === 'ascend' ? 0 : 1
});
},
[loadTableData]
);
useEffect(() => {
_getTableData();
}, [_getTableData]);
return {
loadTableData,
tableProps: {
dataSource: listData,
onChange: handleFilter,
loading,
pagination: {
current: params.pageNum,
pageSize: params.pageSize,
showSizeChanger: true,
showQuickJumper: true,
total,
},
},
};
}
AI 代码解读
使用: