ref不能替代状态管理,因其赋值不触发重新渲染;应使用usestate管理需驱动ui的数据,useref仅用于缓存最新值并配合useeffect同步。

Ref 初始赋值不能代替状态管理
ref 本身不触发重新渲染,所以它不适合存储需要驱动 UI 变化的“初始数据”。如果你用 useRef({}) 或 useRef([]) 存异步返回的初始空数据,后续即使 API 返回了真实数据并赋给 ref.current,组件也不会自动更新——UI 还是旧的。这容易让人误以为“数据没拿到”,其实是“UI 没刷新”。
正确做法:用 useState 管理可渲染的数据,用 useRef 缓存最新值
你需要两个东西分工合作:
- useState 存真实数据(比如 const [data, setData] = useState(null)),确保 setData 后组件重绘
- useRef 存一份当前最新的 data 副本(比如 const dataRef = useRef(data)),在 useEffect、定时器、事件回调等闭包中安全读取最新值
- 每次 setData 后,同步更新 ref:useEffect(() => { dataRef.current = data; }, [data])
处理“初始空数据”的典型流程
比如组件一上来要加载用户信息,但接口可能返回空对象或 null:
- 初始化 state 为 null:const [user, setUser] = useState(null),表示“尚未加载”
- 初始化 ref:const userRef = useRef(user),此时 current 是 null,没问题
- 在 useEffect 中请求数据,成功后调用 setUser(res),触发渲染
- 再加一个 useEffect 监听 user 变化,把新值同步进 ref:useEffect(() => { userRef.current = user; }, [user])
- 后续在 setTimeout 或 event handler 里需要用户 ID,就用 userRef.current?.id,不会卡在初始 null
别把 ref 当作“延迟初始化的 state”
有人想绕过 useState,直接在 ref 上做“懒赋值”:if (!myRef.current) myRef.current = await api()。这不行,因为:
- await 只能在 async 函数里,而 useEffect 回调不能是 async(会返回 Promise,破坏执行逻辑)
- ref 赋值不触发 render,UI 不知道数据已来
- 多次调用该逻辑可能重复请求,没防抖也没竞态控制











