-
前提:最近做的一个需求中,需要根据条件渲染不同的组件,在组件中请求初始化接口,这个接口在请求失败时会进行弹窗提示
-
出现的问题:该组件进行了多次渲染,导致useEffect中的初始化接口发出了多次请求,接口报错后,弹出了多个弹窗
原代码:
// 父组件
const App = ({ ID }) => {
const [info1, setInfo1] = useState()
const [info2, setInfo2] = useState()
// ……省略
const renderMap = useMemo(() => {
return {
// A组件
A: {
content: <A
info1={info1}
/>,
},
// B组件
B: {
content: <B
info1={info1}
info2={info2}
/>,
}
/** useMemo依赖两个info数据,所以在这两个info发生改变时会返回新的renderMap对象 */
}}, [info1, info2])
// 根据props中的ID判断具体展示哪个组件
return <>{renderMap[ID].content}</>
}
export default memo(App)
// 子组件B(这里拿B举例)
const B = () => {
// 页面初始化时请求接口
useEffect(() => {
const search = async () => {
let result = await search().catch((e) => e)
if (result.type === 'fail') {
// 接口请求失败后进行弹窗提示
return Dialog({
title: '失败后的弹窗',
})
}
setData(result)
}
search()
}, [])
return <>
...这里是页面内容
</>
}
export default memo(B)
-
问题原因:在父组件中依赖了多个值进行渲染子组件,导致每个数据发生变化时,都产生了新的子组件对象,子组件进行多次渲染,发出多个初始化请求,当接口报错时,就会弹出多个弹窗
-
解决方案:通过一个字段进行拦截
const B = (props) => {
// 页面初始化时请求接口
useEffect(() => {
// 防止页面多次渲染,接口多次请求,造成弹出多个弹窗问题
let ignore = false
const search = async () => {
let result = await search().catch((e) => e)
/*
关键2:
(1) 由于search是异步请求,所以第一次渲染页面时发出的请求执行到这里时,组件
已经被卸载,此时的ignore已经是true,被拦截后不会走下面的逻辑;
(2) 但是在最后一次渲染时(第二次渲染),因为页面没有被卸载,所以此时ignore还是
false,会正常走下面的逻辑
(3) 故失败后的弹窗以及成功后的setData都是在最后一次渲染页面时才会执行的逻辑,
也就解决了出现的问题
*/
if (ignore) return
if (result.type === 'fail') {
// 接口请求失败后进行弹窗提示
return Dialog({
title: '失败后的弹窗',
})
}
setData(result)
}
search()
/*
关键1:
因为页面多次渲染(例如渲染了2次),那么在第一次渲染结束后,会进行卸载组件,在卸载时
将ignore字段置为true
*/
return () => {
ignore = true;
}
}, [])
return <>
...这里是页面内容
</>
}
export default memo(B)