React Hooks页面多次渲染,导致useEffect中初始化接口发出多次请求

819 阅读2分钟
  1. 前提:最近做的一个需求中,需要根据条件渲染不同的组件,在组件中请求初始化接口,这个接口在请求失败时会进行弹窗提示

  2. 出现的问题:该组件进行了多次渲染,导致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)
  1. 问题原因:在父组件中依赖了多个值进行渲染子组件,导致每个数据发生变化时,都产生了新的子组件对象,子组件进行多次渲染,发出多个初始化请求,当接口报错时,就会弹出多个弹窗

  2. 解决方案:通过一个字段进行拦截

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)
  1. 参考文档:blog.csdn.net/qq_34164814…