我一直看到useCallback
函数似乎无法访问外部作用域的奇怪行为。
此处描述:How does React Hooks useCallback "freezes" the closure?
尽管可以通过将所有内容添加到数组中来轻松地重新计算它,但它似乎违反直觉。所以我想知道什么是最好的处理方法。
这是代码
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
。
尽管这两个变量看起来“有点”,但可能很容易需要更多,只是感觉不对。
有什么办法可以解决此问题或以不同的方式处理它?</ p>
答案 0 :(得分:1)
问题似乎是您在回调中使用了状态变量,然后在useEffect
中对其进行了设置。可能没有简单的方法来处理此问题,请参见here。
两个选项
请勿使用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
和scrollThreshold, 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]);
让我知道是否不清楚。