【问题标题】:React Hooks, prop value changing randomly between rendersReact Hooks,prop 值在渲染之间随机变化
【发布时间】:2020-02-16 19:05:36
【问题描述】:

我正在尝试将一个类组件(一个 Gatsby 布局组件)转换为一个使用反应挂钩来管理状态的功能组件。我的目标是在我们到达页面中的某个点并向上滚动时打开一个模式。

我面临的问题是,我传递给这个布局组件(TrustedInView,见下面的代码)的一个道具正在改变渲染之间的值,正如控制台日志显示的那样。所以我对那里发生的事情感到困惑。我希望 prop 的值始终相同,因为 index.js 中记录的值没有改变(也不应该改变)。

这是布局组件中的代码:

// Hook
const usePrevious = value => {
  const ref = useRef();

  // Store current value in ref
  useEffect(() => {
    ref.current = value;
  }, [value]); // Only re-run if value changes

  // Return previous value (happens before update in useEffect above)
  return ref.current;
};

let notOpenedYet = true;
// Layout component
const Layout = ({ intl, children, trustedInView }) => {
  const [modalIsOpen, setOpen] = useState(false);

  // Get the previous value (was passed into hook on last render)
  let prevPosition = usePrevious(window.scrollY);

  const handleNavigation = e => {
    const custWindow = e.currentTarget;
    if (prevPosition > custWindow.scrollY) {
      console.log('trustedInView when goes up:', trustedInView);
      if (trustedInView && notOpenedYet) {
        notOpenedYet = false;
        setTimeout(() => setOpen(true), 1500);
      }
    }
    prevPosition = custWindow.scrollY;
  };

  useEffect(() => {
    // Add event listener when component mounts:
    window.addEventListener('scroll', e => handleNavigation(e));
    // Remove event listener when component unmounts:
    return window.removeEventListener('scroll', e => handleNavigation(e));
  });

这是带有布局组件的 index.js 渲染方法:

 render() {
    const { intl } = this.props;
    const { activeVideo, tabs, tabNumber, trustedInView } = this.state;
    const features = [
      // some objects
    ];
    console.log('trustedInView in index :', trustedInView);
    return (
      <Layout trustedInView={trustedInView}>

这是控制台日志:

I expect the value of the prop to always be the same

谁能让我走上正轨?

【问题讨论】:

  • 你能把index.js的代码贴出来吗?
  • 如果你希望useEffect 只在挂载上运行,你应该传递[] 作为第二个参数。否则,它将在每次渲染时被调用。
  • removeEventListener 需要引用相同的监听函数才能工作

标签: javascript reactjs react-hooks


【解决方案1】:

在每次渲染中都会设置一个新的侦听器,并且永远不会被删除。因此,在随后的滚动中,您实际上会看到每个事件不止一个 console.log,其中一些通过闭包捕获了不同的 trustedInView,这使得该值似乎在发生变化,但实际上并没有发生变化。

这段代码有几个问题

  1. useEffect 没有任何依赖关系,因此在每次渲染时都会调用它
  2. useEffect removeEventListener 在组件卸载时并没有真正被调用,useEffect 期望您返回一个函数,并且您正在返回 removeEventListener 的任何结果。
  3. 您没有删除任何侦听器,因为您正在创建一个新函数并将其传递给 removeEventListener,而不是将传递给 addEventListener 的函数传递给另一个每次创建的函数。
  4. 每个渲染器都有一个新版本的handleNavigation,因此即使我们要修复以前的错误,我们也必须确保删除正确的侦听器。幸运的是,这就是 useCallback 的用途。

考虑到这些因素将如下所示:

const handleNavigation = useCallback(() => { /* ... */ }, [prevPosition, trustedInView])

useEffect(() => {
  window.addEventListener('scroll', handleNavigation);
  return () => {
    window.removeEventListener('scroll', handleNavigation);
  }
}, [handleNavigation])

但即使这样也是有问题的,因为 prevPosition 在每次破坏效果时都会发生变化,而且对于您的目的来说会变得陈旧。所以最好直接使用 ref 而不使用 usePrevious 钩子

const prevValueRef = useRef(0);

const handleNavigation = useCallback(e => {
  const scrollY = e.currentTarget.scrollY;
  const prevScrollY = prevValueRef.current;

  // Save the value for the next event
  prevValueRef.current = scrollY;

  // ...

}, [trustedInView])

【讨论】:

  • 嗨@Mon Villalon,谢谢它现在很好用!但是我不确定将 handleNavigation 作为 useEffect 的第二个参数是如何工作的。是不是因为里面scrollY的值变化决定了效果被调用了?
  • 每个变量、函数或其他在 useEffect(或 useMemo / useCallback)中使用的变量都应该在数组中指定为钩子的第二个参数,否则会因为闭包捕获而发生奇怪的事情价值观。在评论中解释太长了,但请看reactjs.org/docs/hooks-rules.html
猜你喜欢
  • 1970-01-01
  • 2021-04-21
  • 2021-09-09
  • 1970-01-01
  • 1970-01-01
  • 2021-05-09
  • 2021-06-03
  • 2021-01-02
相关资源
最近更新 更多