React父组件传更新的scrollY给子组件,子组件内部函数取不到最新值如何解决
问题原因
- 核心问题是React函数组件的闭包陷阱:函数组件每次渲染都会生成独立的闭包环境,你在回调函数中访问的
props是当前渲染周期下的快照值,不会自动随组件更新而变化。 - 你子组件中注册的滚动事件回调、递归执行的
requestAnimationFrame的step函数,一旦注册完成,就会一直保留创建时的闭包环境,访问的props.scrollY始终是注册时的值:- 如果你实际运行的代码给子组件的
useEffect加了空依赖数组[],那么useEffect仅在组件首次挂载时执行一次,回调捕获的就是初始值0,后续props更新也不会重新注册回调,所以打印始终为0。 - 如果你贴的代码是完整的(没加空依赖),问题出在递归调用的
step函数始终引用首次注册的函数实例,每次组件重渲染时旧的step还没执行就被清理函数取消,实际运行的始终是最早的step回调,捕获的还是初始值0。
- 如果你实际运行的代码给子组件的
- 补充:你父组件的
useEffect没有加依赖数组,会导致每次父组件重渲染都反复移除、重新注册全局滚动事件,存在严重性能问题。
解决方法
首先修正父组件的性能问题,给useEffect加空依赖数组,仅在组件挂载时注册一次滚动事件:
// 修正后的父组件useEffect useEffect(() => { function onScroll(e) { setScrollY(window.scrollY); } window.addEventListener("scroll", onScroll); return () => { window.removeEventListener("scroll", onScroll); }; }, []); // 加空依赖,仅挂载/卸载时执行一次
子组件可以选择以下两种方案解决闭包问题:
方案1:给useEffect添加props.scrollY依赖
每次scrollY更新时,销毁旧的回调和requestAnimationFrame实例,重新注册捕获了最新props的新回调:
// 修正后的子组件useEffect useEffect(() => { function onScroll() { console.log(props.scrollY); } function step() { console.log(props.scrollY); animationFrame = window.requestAnimationFrame(step); } window.addEventListener("scroll", onScroll); let animationFrame = window.requestAnimationFrame(step); return () => { window.removeEventListener("scroll", onScroll); window.cancelAnimationFrame(animationFrame); }; }, [props.scrollY]); // 把scrollY加入依赖,变化时重新执行effect
方案2:用useRef存储最新的scrollY值
如果你不想每次props更新都反复注册销毁事件,可以用ref存储最新值,回调中访问ref.current即可拿到最新值,不需要修改useEffect依赖:
import { useEffect, useRef } from 'react'; export default function Child(props) { // 定义ref存储最新的scrollY const scrollYRef = useRef(props.scrollY); // 每次props更新时同步更新ref的值 scrollYRef.current = props.scrollY; useEffect(() => { function onScroll() { // 访问ref.current拿最新值 console.log(scrollYRef.current); } function step() { console.log(scrollYRef.current); animationFrame = window.requestAnimationFrame(step); } window.addEventListener("scroll", onScroll); let animationFrame = window.requestAnimationFrame(step); return () => { window.removeEventListener("scroll", onScroll); window.cancelAnimationFrame(animationFrame); }; }, []); // 空依赖,仅执行一次即可 return <div>{props.scrollY}</div> }
内容的提问来源于stack exchange,提问作者김승어
相关产品推荐
相关产品推荐

