0

我有一个基于类的组件,它使用多点触控将子节点添加到 svg,效果很好。现在,我正在尝试更新它以使用带有钩子的功能组件,如果没有其他原因,只是为了更好地理解它们。

为了停止浏览器使用手势的触摸事件,我需要对它们进行preventDefault操作,这要求它们不是被动的,并且由于在我需要使用的合成反应事件中缺乏被动配置的暴露svgRef.current.addEventListener('touchstart', handler, {passive: false})。我在componentDidMount()生命周期钩子中执行此操作,并在类中的钩子中清除它componentWillUnmount()

当我将其转换为带有钩子的功能组件时,我最终得到以下结果:

export default function Board(props) {

    const [touchPoints, setTouchPoints] = useState([]);
    const svg = useRef();

    useEffect(() => {
        console.log('add touch start');
        svg.current.addEventListener('touchstart', handleTouchStart, { passive: false });

        return () => {
            console.log('remove touch start');
            svg.current.removeEventListener('touchstart', handleTouchStart, { passive: false });
        }
    });

    useEffect(() => {
        console.log('add touch move');
        svg.current.addEventListener('touchmove', handleTouchMove, { passive: false });

        return () => {
            console.log('remove touch move');
            svg.current.removeEventListener('touchmove', handleTouchMove, { passive: false });
        }
    });

    useEffect(() => {
        console.log('add touch end');
        svg.current.addEventListener('touchcancel', handleTouchEnd, { passive: false });
        svg.current.addEventListener('touchend', handleTouchEnd, { passive: false });

        return () => {
            console.log('remove touch end');
            svg.current.removeEventListener('touchend', handleTouchEnd, { passive: false });
            svg.current.removeEventListener('touchcancel', handleTouchEnd, { passive: false });
        }
    });


    const handleTouchStart = useCallback((e) => {
        e.preventDefault();

        // copy the state, mutate it, re-apply it
        const tp = touchPoints.slice();

        // note e.changedTouches is a TouchList not an array
        // so we can't map over it
        for (var i = 0; i < e.changedTouches.length; i++) {
            const touch = e.changedTouches[i];
            tp.push(touch);
        }
        setTouchPoints(tp);
    }, [touchPoints, setTouchPoints]);

    const handleTouchMove = useCallback((e) => {
        e.preventDefault();

        const tp = touchPoints.slice();

        for (var i = 0; i < e.changedTouches.length; i++) {
            const touch = e.changedTouches[i];
            // call helper function to get the Id of the touch
            const index = getTouchIndexById(tp, touch);
            if (index < 0) continue;
            tp[index] = touch;
        }

        setTouchPoints(tp);
    }, [touchPoints, setTouchPoints]);

    const handleTouchEnd = useCallback((e) => {
        e.preventDefault();

        const tp = touchPoints.slice();

        for (var i = 0; i < e.changedTouches.length; i++) {
            const touch = e.changedTouches[i];
            const index = getTouchIndexById(tp, touch);
            tp.splice(index, 1);

        }

        setTouchPoints(tp);
    }, [touchPoints, setTouchPoints]);

    return (
        <svg 
            xmlns={ vars.SVG_NS }
            width={ window.innerWidth }
            height={ window.innerHeight }
        >
            { 
                touchPoints.map(touchpoint =>
                    <TouchCircle 
                        ref={ svg }
                        key={ touchpoint.identifier }
                        cx={ touchpoint.pageX }
                        cy={ touchpoint.pageY }
                        colour={ generateColour() }
                    />
                )
            }
        </svg>
    );
}

这引发的问题是,每次渲染更新时,事件侦听器都会被删除并重新添加。这会导致 handleTouchEnd 在它有机会清除其他奇怪的添加触摸之前被移除。我还发现触摸事件不起作用,除非我使用手势退出触发更新的浏览器,删除现有的侦听器并添加新的集合。

我尝试在 useEffect 中使用依赖项列表,并且我看到几个人引用 useCallback 和 useRef 但我无法使这项工作更好(即,删除然后重新添加事件侦听器的日志仍然每次更新都会触发)。

有没有办法让 useEffect 只在挂载时触发一次,然后在卸载时清理,或者我应该放弃这个组件的钩子并坚持使用基于类的运行良好的钩子?

编辑

我还尝试将每个事件侦听器移到自己的位置useEffect并获取以下控制台日志:

remove touch start
remove touch move
remove touch end
add touch start
add touch move
add touch end

编辑 2

有几个人建议添加一个我尝试过的依赖数组:

    useEffect(() => {
        console.log('add touch start');
        svg.current.addEventListener('touchstart', handleTouchStart, { passive: false });

        return () => {
            console.log('remove touch start');
            svg.current.removeEventListener('touchstart', handleTouchStart, { passive: false });
        }
    }, [handleTouchStart]);

    useEffect(() => {
        console.log('add touch move');
        svg.current.addEventListener('touchmove', handleTouchMove, { passive: false });

        return () => {
            console.log('remove touch move');
            svg.current.removeEventListener('touchmove', handleTouchMove, { passive: false });
        }
    }, [handleTouchMove]);

    useEffect(() => {
        console.log('add touch end');
        svg.current.addEventListener('touchcancel', handleTouchEnd, { passive: false });
        svg.current.addEventListener('touchend', handleTouchEnd, { passive: false });

        return () => {
            console.log('remove touch end');
            svg.current.removeEventListener('touchend', handleTouchEnd, { passive: false });
            svg.current.removeEventListener('touchcancel', handleTouchEnd, { passive: false });
        }
    }, [handleTouchEnd]);

但我仍然收到一条日志,说每个useEffects 都已被删除,然后在每次更新时重新添加(所以每个touchstarttouchmove或者touchend导致油漆 - 这很多:))

编辑 3

我已经替换window.(add/remove)EventListeneruseRef()

ta

4

2 回答 2

3

如果您只希望在安装和卸载组件时发生这种情况,则需要为 useEffect 钩子提供一个空数组作为依赖数组

useEffect(() => {
    console.log('adding event listeners');
    window.addEventListener('touchstart', handleTouchStart, { passive: false });
    window.addEventListener('touchend', handleTouchEnd, { passive: false });
    window.addEventListener('touchcancel', handleTouchEnd, { passive: false });
    window.addEventListener('touchmove', handleTouchMove, { passive: false });

    return () => {
        console.log('removing event listeners');
        window.removeEventListener('touchstart', handleTouchStart, { passive: false });
        window.removeEventListener('touchend', handleTouchEnd, { passive: false });
        window.removeEventListener('touchcancel', handleTouchEnd, { passive: false });
        window.removeEventListener('touchmove', handleTouchMove, { passive: false });
    }
}, []);
于 2020-06-25T09:47:59.730 回答
1

非常感谢大家 - 我们已经找到了它的底部(w00t)

为了停止组件useEffect钩子多次触发,需要为钩子提供一个空的依赖数组(如Son Nguyengojun所建议的),但这意味着touchPoints在处理程序中无法访问当前状态。

答案(由 gojun 建议)在How to fix missing dependency warning when using useEffect React Hook?

其中提到了钩子常见问题解答:https ://reactjs.org/docs/hooks-faq.html#what-c​​an-i-do-if-my-effect-dependencies-change-too-often

这就是我的组件的最终结果

export default function Board(props) {
    const [touchPoints, setTouchPoints] = useState([]);
    const svg = useRef();

    useEffect(() => {
        // required for the return value
        const svgRef = svg.current;

        const handleTouchStart = (e) => {
            e.preventDefault();

            // use functional version of mutator
            setTouchPoints(tp => {
                // duplicate array
                tp = tp.slice();

                // note e.changedTouches is a TouchList not an array
                // so we can't map over it
                for (var i = 0; i < e.changedTouches.length; i++) {
                    const touch = e.changedTouches[i];
                    const angle = getAngleFromCenter(touch.pageX, touch.pageY);

                    tp.push({ touch, angle });
                }

                return tp;
            });
        };

        const handleTouchMove = (e) => {
            e.preventDefault();

            setTouchPoints(tp => {
                tp = tp.slice();

                // move existing TouchCircle with same key
                for (var i = 0; i < e.changedTouches.length; i++) {
                    const touch = e.changedTouches[i];
                    const index = getTouchIndexById(tp, touch);
                    if (index < 0) continue;
                    tp[index].touch = touch;
                    tp[index].angle = getAngleFromCenter(touch.pageX, touch.pageY);
                }

                return tp;
            });
        };

        const handleTouchEnd = (e) => {
            e.preventDefault();

            setTouchPoints(tp => {
                tp = tp.slice();

                // delete existing TouchCircle with same key
                for (var i = 0; i < e.changedTouches.length; i++) {
                    const touch = e.changedTouches[i];
                    const index = getTouchIndexById(tp, touch);
                    if (index < 0) continue;
                    tp.splice(index, 1);
                }

                return tp;
            });
        };

        console.log('add touch listeners'); // only fires once
        svgRef.addEventListener('touchstart', handleTouchStart, { passive: false });
        svgRef.addEventListener('touchmove', handleTouchMove, { passive: false });
        svgRef.addEventListener('touchcancel', handleTouchEnd, { passive: false });
        svgRef.addEventListener('touchend', handleTouchEnd, { passive: false });

        return () => {
            console.log('remove touch listeners');
            svgRef.removeEventListener('touchstart', handleTouchStart, { passive: false });
            svgRef.removeEventListener('touchmove', handleTouchMove, { passive: false });
            svgRef.removeEventListener('touchend', handleTouchEnd, { passive: false });
            svgRef.removeEventListener('touchcancel', handleTouchEnd, { passive: false });
        }
    }, [setTouchPoints]);

    return (
        <svg 
            ref={ svg }
            xmlns={ vars.SVG_NS }
            width={ window.innerWidth }
            height={ window.innerHeight }
        >
            { 
                touchPoints.map(touchpoint =>
                    <TouchCircle 
                        key={ touchpoint.touch.identifier }
                        cx={ touchpoint.touch.pageX }
                        cy={ touchpoint.touch.pageY }
                        colour={ generateColour() }
                    />
                )
            }
        </svg>
    );
}

注意:我添加setTouchPoints到依赖列表中以更具声明性

Mondo尊重人

;oB

于 2020-06-25T14:52:59.170 回答