1. 依賴項沒指定好
hook是利用閉包的特性來生成對應的方法;
當不傳依賴項,方法內部的狀態值都是取的在定義hook的初始值;
當傳入了依賴項,那麼依賴項值發生改變,hook會被更新,這個時候它內部用的變量也都會更新到最新。
所以,如果hook裏用了狀態變量,一定要記得作爲依賴項傳入,否則會遇到坑哦。
尤其是hook之間互相調用的時候,很容易指定不好依賴項。
如下例子:
本意:merchantChartQuery作爲fetchOrder的依賴項,控制fetchOrder的更新。當執行setCpsOrderStatus方法的時候,調用fetchOrder的方法,利用最新的數據來查詢接口。
結果:merchantChartQuery更新後,執行setCpsOrderStatus方法時,fetchOrder還是用了舊的數據去查詢接口。
// props裏傳入merchantChartQuery作爲查詢參數
const View: React.FunctionComponent = props => {
// 從props裏取一個變量
const { merchantChartQuery } = props;
// 利用最新的merchantChartQuery數據去接口查詢數據
const fetchOrder = useCallback(params => {
fetchMerchantOrderData({
page: 1,
...merchantChartQuery,
...params
});
}, [merchantChartQuery]);
// 錯誤寫法
// 這裏不寫依賴項時,雖然上面fetchOrder更新裏,
// 但是這裏取得還是舊的fetchOrder,導致查詢時仍然用的就數據查詢
const setCpsOrderStatus = useCallback(cpsOrderStatus => {
fetchOrder({ cpsOrderStatus });
}, []);
// 正確寫法
const setCpsOrderStatus = useCallback(cpsOrderStatus => {
fetchOrder({ cpsOrderStatus });
}, [fetchOrder]);
}
2. hook利用Object.is來判斷依賴項是否更新
hook判斷一個依賴項是否發生更新,利用的是Object.is方法(內部是===來對比);
基本類型變量的比較基本不會有什麼困擾,引用類型就需要注意了。
如下例子:
本意:根據用戶的權限不用,reportTabs數組(控制頁面導航的顯示內容)也是不同的,希望在useEffect中通過權限查詢結果來更新數組內容。這樣不同權限的用戶會看到不同的導航內容。
結果:由於useState中使用了initReportTabs作爲初始值,獲得權限後更新時也是通過引用initReportTabs來處理的,導致Object.is在比較的時候,認爲變量沒有改變,不會更新組件,會導致頁面顯示與實際數據不符。
interface IReportTabTypes {
key: string;
title: string;
}
// reportTabs的初始值
const initReportTabs = [
{
key: 'goods-overview',
title: '商品佣金報表',
}
];
const View: React.FunctionComponent = () => {
const [reportTabs, setReportTabs] = useState<IReportTabTypes[]>(initReportTabs);
useEffect(() => {
merchantApi.fetchIsTuanV2Header().then(res => {
// 引用類型的變量,指向的是同一個內存變量
const list = initReportTabs;
res && list.push({
key: 'merchant-overview',
title: '團長佣金報表',
});
// 無效寫法
setReportTabs(list);
// 有效寫法,傳入的是一個新的變量,hook認爲他是發生更新,進而會更新組件
setReportTabs([...list]);
});
}, []);
}
3. hook內是一個閉包
如下例子:
本意:通過按鈕事件,控制彈框展示,彈框內是一個異步加載的級聯選擇框。
結果: 彈框裏的級聯選擇框內容並沒有隨着數據的更新而實時展示出來
const View: React.FunctionComponent = () => {
// 級聯選擇框的選項列表
const [categoryList, setCategoryList] = useState<any[]>([]);
// 級聯選擇框異步加載的方法
const onLoadMore = useCallback(option => {
option.loading = true;
// 異步查詢子類數據,更新categoryList特定項的children數組內容
api.listChildren({
pid: option.id,
channel: 1,
}).then(data => {
option.children = data.map(item => {
return {
...item,
isLeaf: true,
};
});
}).finally(() => {
option.loading = false;
const list = [ ...categoryList ];
setCategoryList(list);
});
}, [categoryList]);
// 調用handleOpenDialog方法後,創建一個彈框,這個時候的彈框是在一個閉包中,外部數據更新只是能引起handleOpenDialog方法的更新,彈框中的狀態無法被改變
// 所以雖然接口請求到了新的數據,categoryList發生了更新,但是級聯框的內容依然是舊的
const handleOpenDialog = useCallback(() => {
Dialog.open({
title: '選擇類目',
content: <Cascader
filterable={true}
value={item.ids}
options={categoryList}
loadMore={onLoadMore}
propsAlias={{label: 'name', id: 'id'}}
/>
footer:
})
}, [categoryList]);
return (
<div>
<button onClick={handleOpenDialog}>顯示彈窗</button>
</div>
)
}