React 高效使用事件委托
我们呐,一直在背诵八股文,也一直有一个十分细节的优化方案:就是利用事件委托的机制进行代码的性能优化!!!
但是如何更好的利用这个机制呐????🤔🤔
为什么使用事件委托呐???
-
- 为了减少DOM事件的频繁绑定,操作DOM事件是十分的昂贵的呐
-
- 事件的频繁绑定消耗内存,使用事件委托,从而达到内存一定的优化吧
-
- 方便后期的维护,以及代码的扩展性
我不是文学家,就简单说说我自己的理解吧😄😄
事件委托的实现步骤
-
首先确定需要进行事件委托的部分是哪一个???
- 例如: 如果说是一个列表元素,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;
完了,感觉好久没有写过文章,已经不会写了(一时的外向,换来了一辈子的内向😭😭😭)
今天就简单写到这里吧(好久不写,真心感觉很生疏呀)