防止 eventListener 多次触发

Prevent eventListener from firing multiple times

我正在创建一个 HOC,如果输入组件有任何锚标记,它会添加从外部打开超链接的功能。

下面是 HOC:

export const withExternalLink = ( Component, rootId = false ) => ( props ) => {
    function openLinksExternally( e, rootId ) {
        const rootHtml = e.target.closest( rootId );

        if ( ! rootHtml ) {
            return;
        }

        const anchor = e.target.closest( 'a' );

        if ( anchor ) {
            e.preventDefault();

            /**
             * Logic to open link externally.
             */
        }
    }

    useEffect( () => {
        document.addEventListener( 'click', ( e ) => openLinksExternally( e, rootId ) );
        return () => document.removeEventListener( 'click', ( e ) => openLinksExternally( e, rootId ) );
    }, [] );

    return <Component { ...props } />;
};

用法:

const CardWithExternalLinks = withExternalLink( Card );

如果 <CardWithExternalLinks /> 在页面上使用一次,此方法有效。但是如果我们使用它 n 次,那么 useEffect() 运行 n 次,然后添加事件侦听器 n 次。

我能够通过将 openLinksExternally() 移到 withExternalLink() 之外来解决它,这样只有 1 个函数引用。但问题是我必须使用匿名函数来添加事件侦听器,因为我必须将 rootId 参数传递给 openLinksExternally().

因为我使用了匿名函数,所以每个 useEffect() 都会注册一个新的事件侦听器,每当单击 <a/> 标签时,openLinksExternally() 运行 n 次.

有没有更好的实现方法,让点击运行一次?

我用闭包解决了这个问题,这是我的解决方案:

export const withExternalLink = ( Component ) => ( rootId = false ) => {
    let isListenerAttached = false;

    return ( props ) => {
        function openLinksExternally( e, id ) {
            if ( ! id ) {
                return;
            }

            const rootHtml = e.target.closest( id );

            if ( ! rootHtml ) {
                return;
            }

            const anchor = e.target.closest( 'a' );

            if ( anchor ) {
                anchor.setAttribute( 'target', '_blank' );
            }
        }

        useEffect( () => {
            if ( ! isListenerAttached ) {
                document.addEventListener( 'click', ( e ) => openLinksExternally( e, rootId ) );
                isListenerAttached = true;
                return;
            }
            return () => document.removeEventListener( 'click', ( e ) => openLinksExternally( e, rootId ) );
        }, [] );

        return <Component { ...props } />;
    };
};