React Hook 遇到的小坑--持续记录
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>
)
}
还没有评论,来说两句吧...