javascript - 使用 useCallback/useMemo 冻结闭包
问题描述
我一直看到useCallback
函数似乎无法访问外部范围的奇怪行为。
此处对此进行了描述:React Hooks 如何使用Callback“冻结”闭包?
尽管可以通过将所有内容添加到数组中轻松地重新计算它,但它看起来确实违反直觉。所以我想知道处理它的最佳方法是什么。
这是代码
import React, { useCallback, useState, useEffect, useRef } from 'react';
import classNames from 'classnames';
import useEventListener from '../../hooks/use-event-listener';
import tinybounce from 'tinybounce';
import Scroll from '../../utils/scroll';
export default function DefaultMainPlayer({ children, className }) {
const [scrollThreshold, setScrollThreshold] = useState(0);
const [inView, setInView] = useState(true);
const [didUserClose, setDidUserClose] = useState(false);
const el = useRef();
const scrollHandler = useCallback(() => {
// scrollThreshold and inView get "frozen" unless they're on the array below
const isVisible = Scroll.getPosition() <= scrollThreshold;
if (inView !== isVisible) {
if (inView) {
setInView(isVisible);
setDidUserClose(false);
}
}
}, [scrollThreshold, inView]);
const cx = classNames('main-player', className, {
'is-fixed': !inView && !didUserClose
});
const resizeHandler = useCallback(
tinybounce(() => setScrollThreshold(Scroll.getElementCoordinates(el.current).bottom), 300),
[]
);
// If scroll threshold updates, lets call the scroll handler
useEffect(scrollHandler, [scrollThreshold]);
// Call the resize handler once
useEffect(resizeHandler, []);
// I'd like the scroll handler to never change since it really doesn't need to
useEventListener('scroll', scrollHandler);
useEventListener('resize', resizeHandler);
return (
<div className={cx} ref={el}>{children}</div>
);
}
这是`use-event-listener`的代码
import { useRef, useEffect } from 'react';
export default function useEventListener(eventName, handler, element = window) {
const savedHandler = useRef();
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(() => {
const isSupported = element && element.addEventListener;
if (!isSupported) return;
const eventListener = event => savedHandler.current(event);
// Add event listener
element.addEventListener(eventName, eventListener);
// Remove event listener on cleanup
return () => {
element.removeEventListener(eventName, eventListener);
};
},
[eventName, element]
);
};
我试图避免scrollHandler
一直在变化,因为功能并不相同。我已经尝试过useMemo
(将函数作为值返回),但结果是一样的。不要scrollThreshold
更新。
尽管这两个变量看起来“有点”,但它很容易需要更多,而且感觉不对。
有没有办法解决这个问题或以不同的方式处理它?
解决方案
问题似乎是您在回调中使用了一个状态变量,然后将其设置在useEffect
. 可能没有简单的方法来处理这个问题,请参见此处。
两种选择
不要使用useCallback
. 这样,在运行该功能时,它将使用最新的scrollThreshold
和inView
. 还将不断变化的变量传递给useEventListener
,或任何使用它的钩子,作为 的依赖项useEffect
(这需要一些重构):
useEffect(() => {
const isSupported = element && element.addEventListener;
if (!isSupported) return;
const eventListener = handler;
// Add event listener
element.addEventListener(eventName, eventListener);
// Remove event listener on cleanup
return () => {
element.removeEventListener(eventName, eventListener);
};
}, [eventName, element, scrollThreshold, inView]);
使用useCallback
withscrollThreshold, inView
作为依赖项。在useEventListener
或任何使用它的钩子中,使用在添加/删除事件处理程序中作为依赖项传递的函数:
useEffect(() => {
const isSupported = element && element.addEventListener;
if (!isSupported) return;
const eventListener = handler;
// Add event listener
element.addEventListener(eventName, eventListener);
// Remove event listener on cleanup
return () => {
element.removeEventListener(eventName, eventListener);
};
}, [eventName, element, handler]);
如果有不清楚的地方,请告诉我。
推荐阅读
- javascript - 从同级组件访问组件中的 ref
- java - 如何在放心断言中传递存储在 Map 中的期望值
- java - Android中的OpenCV说输入图像中的通道数无效
- wordpress - WordPress 编辑/创建标签挂钩未触发
- python - 如何在 Mysql 的条件中使用 Python 列表
- netsuite - SuiteScript 尚不支持以下操作或用例
- julia - 环境从默认环境继承包
- php - 打开powershell和powershell打开excel的PHP脚本
- c# - 使用 Html Agility Pack 选择所有以某个文本值开头的段落
- cudd - CUDD 上的多态 DD(Python 版本,tulip-DD)