使用useCallback / useMemo冻结关闭

时间:2019-09-22 10:06:19

标签: javascript reactjs react-hooks

我一直看到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>

1 个答案:

答案 0 :(得分:1)

问题似乎是您在回调中使用了状态变量,然后在useEffect中对其进行了设置。可能没有简单的方法来处理此问题,请参见here

两个选项


请勿使用useCallback。这样,在运行函数时,它将使用最新的scrollThresholdinView。还将变化的变量作为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]);

使用useCallbackscrollThreshold, 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]);

让我知道是否不清楚。