React自定义Hook如何等待useEffect执行完成后再返回值
问题根因
出现初始返回空值、布局错乱的核心原因是你把布局类名的计算逻辑放在了useEffect中执行。React的执行顺序决定了useEffect一定是在组件首次渲染完成、DOM提交之后才会触发:首次渲染时Hook用的是你手动设置的空字符串初始值,直接把带空类名的内容渲染到页面上,等useEffect执行完更新内部状态,才会触发第二次渲染返回正确类名,自然会出现布局跳变。
修复方案
布局类名是完全可以通过入参data长度、connector直接推导出来的派生值,不需要放到副作用里等渲染完成后再计算,优先用同步计算的方式实现,首次渲染就能拿到正确结果。
方案1:直接同步计算(推荐,无额外性能开销)
删掉Hook内部存类名的useState和对应useEffect逻辑,直接在Hook执行时同步计算类名返回即可,从根源上避免空值渲染:
const useGridLayout = (data = [], connector = '-') => { // 直接替换成你实际的类名计算规则 const dataLen = data.length; let gridClass = ''; if (dataLen === 1) gridClass = `grid${connector}cols${connector}1`; else if (dataLen === 2) gridClass = `grid${connector}cols${connector}2`; else if (dataLen % 3 === 0) gridClass = `grid${connector}cols${connector}3`; else gridClass = `grid${connector}cols${connector}2`; return gridClass; };
如果类名计算逻辑比较复杂、入参变化不频繁,可以用useMemo缓存计算结果,依然是首次渲染同步返回正确值,不会有空窗期:
import { useMemo } from 'react'; const useGridLayout = (data = [], connector = '-') => { return useMemo(() => { const dataLen = data.length; let gridClass = ''; if (dataLen === 1) gridClass = `grid${connector}cols${connector}1`; else if (dataLen === 2) gridClass = `grid${connector}cols${connector}2`; else if (dataLen % 3 === 0) gridClass = `grid${connector}cols${connector}3`; else gridClass = `grid${connector}cols${connector}2`; return gridClass; }, [data.length, connector]); };
方案2:必须保留内部状态的场景
如果有特殊逻辑要求必须用useState存储类名状态,不要把初始值设为空字符串,直接用初始入参计算出初始值即可,首次渲染也能拿到正确结果:
import { useState, useEffect } from 'react'; const useGridLayout = (data = [], connector = '-') => { // 抽离公共计算逻辑 const calcGridClass = (len, conn) => { if (len === 1) return `grid${conn}cols${conn}1`; if (len === 2) return `grid${conn}cols${conn}2`; if (len % 3 === 0) return `grid${conn}cols${conn}3`; return `grid${conn}cols${conn}2`; }; // 初始值直接通过初始入参计算,不要放空字符串 const [gridClass, setGridClass] = useState(() => calcGridClass(data.length, connector)); // 入参变化时同步更新 useEffect(() => { setGridClass(calcGridClass(data.length, connector)); }, [data.length, connector]); return gridClass; };
优化提示
如果data是接口异步返回的,初始渲染时data为空数组,可以在Hook里加加载态判断:接口请求中返回占位布局对应的类名,等数据返回后再计算实际网格类名,体验会比空类名好很多。
不要把可以通过现有props/状态直接推导出来的值单独存为状态、放到useEffect里更新,这类写法很容易导致首次渲染值异常、状态不同步、多余重渲染等问题。
内容的提问来源于stack exchange,提问作者itzzmeakhi
相关产品推荐
相关产品推荐

