React 高效使用事件委托

162 阅读4分钟

React 高效使用事件委托

我们呐,一直在背诵八股文,也一直有一个十分细节的优化方案:就是利用事件委托的机制进行代码的性能优化!!!

但是如何更好的利用这个机制呐????🤔🤔

为什么使用事件委托呐???

    1. 为了减少DOM事件的频繁绑定,操作DOM事件是十分的昂贵的呐
    1. 事件的频繁绑定消耗内存,使用事件委托,从而达到内存一定的优化吧
    1. 方便后期的维护,以及代码的扩展性

我不是文学家,就简单说说我自己的理解吧😄😄

事件委托的实现步骤

  • 首先确定需要进行事件委托的部分是哪一个???

    • 例如: 如果说是一个列表元素,ul > li * n 这样的元素结构,那么我们的事件绑定位置就是 ul元素 上
  • 然后寻找需要确定事件委托的元素特征是什么,你只有知道对应的元素特征后,才可以实现快速的寻找到元素位置确定需要绑定事件的元素

  • 最后就是思考清楚每个事件需要绑定的具体处理逻辑是什么

  • 主要就是这二部曲吧

下面直接进入主题吧!!!不说这些废话了

先来一段需要优化的代码初版(比较简单该组件)

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';

interface GalleryItemProps {
  children?: React.ReactNode;
  width: number;
  height: number;
  title?: string;
  src: string;
  isActive?: boolean;
  id: string;
  onClick?: () => void;
}

const GalleryItem: React.FC<GalleryItemProps> 
= (props: GalleryItemProps) => {
  const { height, title, src, isActive, id } = props
  const [isSave, setIsSave] = useState(false)
  const navigate = useNavigate();

  const handleClick = () => {
      setIsSave(prev => !prev)
  }
  
  const handleUploadClick = () => {
      navigate(
        `/upload`,
      );
  }
  
  const handleMoreClick = () => {
      navigate(
        `/pin/${id}`,
        { state: { id }, }
      );
  }
  
  return (
    <React.Fragment>
      <span 
        className={`gallery-item`}
        style={{
          gridRowEnd: `span ${Math.ceil(height / 200)}`,
          cursor: isActive ? 'pointer' : 'default',
        }}
        onClick={isActive ? handleClick : undefined}
      >
        <img 
          src={src}
          alt={title || 'image'} 
          loading='lazy'
          title={title || 'image'}
        />
        {isActive && (
          <>
            <div className='gallery-see'>
              <button>
                { isSave? 'Saved' : 'Save' }
              </button>
            </div>
            <div className='gallery-cover'></div>
            <div className='gallery-iterator'>
              <div className='gallery-iterator-left'></div>
              <div className='gallery-iterator-right'>
                <div>
                  <img 
                    src="/upload.svg" 
                    alt="upload" 
                    loading='lazy'
                    title='upload'
                    onClick={handleUploadClick}
                  />
                </div>
                <div>
                  <img 
                    src="/more.svg" 
                    alt="more" 
                    loading='lazy'
                    title='more'
                    onClick={handleMoreClick}
                  />
                </div>
              </div>
            </div>
          </>
        )}
      </span>
    </React.Fragment>
  )
}

export default GalleryItem;
  • 先分析代码:
    • 首先这里我们可以发现: button元素,img-upload元素,img-more元素都分别绑定了 onClick 事件
    • 然后就是我们在组件中进行创建了三个不同的处理函数,分别绑定在了不同的元素上
    • 所以说此时我们该如何进行利用事件委托来实现对应的问题呐???

提前讲述一下匹配元素的方法吧

  • 为了快速的使用一个事件,然后快速的寻找到对应的元素位置,有一些API我们是不得不提前了解一下的呐

1. elementInstance.contains(SelectorElement)

  • ref 就是 react 中使用 useRef 进行创建的一个 ref 引用,ref.current 就是获取绑定元素的实例
    • 还可以通过合成事件 event.target 获取得到吧
  • SelectorElement 就是需要进行获取到的元素实例
  • contains 就是一些原生的 API 使用吧(这也是我为什么喜欢 react 的原因吧,支持很多原生 API 的使用,以及也是推荐可以使用的呐)
// 先来一个简单的 DEMO 吧
const handleContainerClick = (e: React.MouseEvent<HTMLElement>) => {
  const target = e.target as HTMLElement;
  const saveButton = e.currentTarget.querySelector('button');
  const uploadIcon = e.currentTarget.querySelector('img[src="/upload.svg"]');
  const moreIcon = e.currentTarget.querySelector('img[src="/more.svg"]');

  if (saveButton && saveButton.contains(target)) {
    setIsSave(prev => !prev);
  } else if (uploadIcon && uploadIcon.contains(target)) {
    navigate('/upload');
  } else if (moreIcon && moreIcon.contains(target)) {
    navigate(`/pin/${id}`, { state: { id } });
  }
};

2. 自定义 data-* 属性进行匹配

  • data-* 原本是在小程序中支持的,但是在现在的浏览器中也是已经支持
const handleContainerClick = (e: React.MouseEvent<HTMLElement>) => {
  const target = e.target as HTMLElement;
  const action = target.getAttribute('data-action');

  switch (action) {
    case 'save':
      setIsSave(prev => !prev);
      break;
    case 'upload':
      navigate('/upload');
      break;
    case 'more':
      navigate(`/pin/${id}`, { state: { id } });
      break;
    default:
      break;
  }
};

3. closest(selectorname) 方法实现获取

const handleContainerClick = (e: React.MouseEvent<HTMLElement>) => {
    const target = e.target as HTMLElement;
    if (target.closest('button')) {
      setIsSave(prev => !prev);
    } else if (target.closest('img[src="/upload.svg"]')) {
      navigate(
        `/upload`,
      );
    } else if (target.closest('img[src="/more.svg"]')) {
      navigate(
        `/pin/${id}`,
        { state: { id }, }
      );
    }
  };

4. matches(selectorname) 方法实现获取

const handleContainerClick = (e: React.MouseEvent<HTMLElement>) => {
    const target = e.target as HTMLElement;
    if (target.closest('button')) {
      setIsSave(prev => !prev);
    } else if (target.closest('img[src="/upload.svg"]')) {
      navigate(
        `/upload`,
      );
    } else if (target.closest('img[src="/more.svg"]')) {
      navigate(
        `/pin/${id}`,
        { state: { id }, }
      );
    }
  };
  • 不管是什么方法,都是实际选择的一种策略吧,都是为了帮助我们快速寻找到元素即可吧

最终落地实现

  • 结合上面的简单的描述和引导,我的选择是使用的是 closest 来帮助我实现对应的功能实现吧
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';

interface GalleryItemProps {
  children?: React.ReactNode;
  width: number;
  height: number;
  title?: string;
  src: string;
  isActive?: boolean;
  id: string;
  onClick?: () => void;
}

const GalleryItem: React.FC<GalleryItemProps> 
= (props: GalleryItemProps) => {
  const { height, title, src, isActive, id } = props
  const [isSave, setIsSave] = useState(false)
  const navigate = useNavigate();

  const handleContainerClick = (e: React.MouseEvent<HTMLElement>) => {
    const target = e.target as HTMLElement;
    if (target.closest('button')) {
      setIsSave(prev => !prev);
    } else if (target.closest('img[src="/upload.svg"]')) {
      navigate(
        `/upload`,
      );
    } else if (target.closest('img[src="/more.svg"]')) {
      navigate(
        `/pin/${id}`,
        { state: { id }, }
      );
    }
  };
  
  return (
    <React.Fragment>
      <span 
        className={`gallery-item`}
        style={{
          gridRowEnd: `span ${Math.ceil(height / 200)}`,
          cursor: isActive ? 'pointer' : 'default',
        }}
        onClick={isActive ? handleContainerClick : undefined}
      >
        <img 
          src={src}
          alt={title || 'image'} 
          loading='lazy'
          title={title || 'image'}
        />
        {isActive && (
          <>
            <div className='gallery-see'>
              <button>
                { isSave? 'Saved' : 'Save' }
              </button>
            </div>
            <div className='gallery-cover'></div>
            <div className='gallery-iterator'>
              <div className='gallery-iterator-left'></div>
              <div className='gallery-iterator-right'>
                <div>
                  <img 
                    src="/upload.svg" 
                    alt="upload" 
                    loading='lazy'
                    title='upload'
                  />
                </div>
                <div>
                  <img 
                    src="/more.svg" 
                    alt="more" 
                    loading='lazy'
                    title='more'
                  />
                </div>
              </div>
            </div>
          </>
        )}
      </span>
    </React.Fragment>
  )
}

export default GalleryItem;

完了,感觉好久没有写过文章,已经不会写了(一时的外向,换来了一辈子的内向😭😭😭)

今天就简单写到这里吧(好久不写,真心感觉很生疏呀)