为什么要使用自定义 Hooks
自定义 Hooks 是 React 中一种复用逻辑的机制,通过它们可以抽离组件中的逻辑,使代码更加简洁、易读、易维护。它们可以在多个组件中复用相同的逻辑,减少重复代码。
1、useThrottle
代码
import React,{ useRef, useState,useEffect } from "react";
/**
* useThrottle:一个节流的 hook,用于限制状态更新的频率。
*
* @param {any} initialState 初始状态
* @param {number} delay 节流间隔时间,默认为 500 毫秒
* @returns {any} 节流后的状态
*/
export const useThrottle = (initialState, delay = 5000) => {
const [state, setState] = useState(initialState);
const timeout = useRef();
const nextValue = useRef(null);
const hasNextValue = useRef(false);
useEffect(() => {
if (timeout.current) {
nextValue.current = initialState;
hasNextValue.current = true;
} else {
setState(initialState);
const timeoutCallback = () => {
if (hasNextValue.current) {
setState(nextValue.current);
hasNextValue.current = false;
}
timeout.current = undefined;
};
timeout.current = setTimeout(timeoutCallback, delay);
}
return () => {
timeout.current && clearTimeout(timeout.current);
}
}, [initialState]);
return state;
};
用法
import { useThrottle } from './useThrottle';
const value = useThrottle(state, 500);
2、useVirtual
代码
import { useEffect, useState } from 'react';
/**
* useVirtual:一个虚拟滚动的 hook,用于优化长列表的渲染性能。
*
* @param {object} listRef 列表的引用对象
* @param {Array} list 初始列表数据
* @param {boolean} isFullScreen 是否全屏显示
* @returns {Array} 显示在视图中的列表数据和 padding 样式
*/
export const useVirtual = (listRef, list, isFullScreen) => {
const origin = list;
let viewHeight = null;
let itemHeight = 0;
let dur = 0;
const rootFontSize = parseInt(document.documentElement.style.fontSize);
const [viewList, setViewList] = useState(list);
const [startIndex, setStartIndex] = useState(0);
const [endIndex, setEndIndex] = useState(0);
const [padding, setPadding] = useState({
paddingTop: 0,
paddingBottom: 0,
});
useEffect(() => {
init(listRef);
}, []);
useEffect(() => {
initData(listRef.current);
update();
}, [startIndex]);
function init(ref) {
initData(ref.current);
render(startIndex, dur + 1);
eventBind(ref.current);
}
function initData(dom) {
const target = isFullScreen ? document.documentElement : dom;
viewHeight = isFullScreen ? target.offsetHeight : target.parentNode.offsetHeight;
itemHeight = target.getElementsByClassName('virtual-item')[0].offsetHeight;
dur = Math.floor(viewHeight / itemHeight);
}
function eventBind(dom) {
const eventTarget = isFullScreen ? window : dom.parentNode;
eventTarget.addEventListener('scroll', handleScroll, false);
}
function render(startIndex, endIndex) {
setViewList(() => origin.slice(startIndex, endIndex));
setEndIndex(() => startIndex + dur + 1);
}
function handleScroll(e) {
e.stopPropagation();
const target = isFullScreen ? document.documentElement : listRef.current.parentNode;
setStartIndex(() => Math.floor(target.scrollTop / itemHeight));
}
function update() {
if (startIndex === endIndex) return;
setEndIndex(() => startIndex + dur);
render(startIndex, endIndex);
setPadding(() => {
return {
paddingTop: (startIndex * itemHeight) / rootFontSize,
paddingBottom: ((origin.length - endIndex) * itemHeight) / rootFontSize,
};
});
}
return [viewList, padding];
};
用法
import { useRef } from 'react';
import { useVirtual } from './useVirtual';
const listRef = useRef();
const [viewList, padding] = useVirtual(listRef, initialList, false);
3、useCopyToClipboard
代码
import { message } from 'antd';
/**
* useCopyToClipboard:一个 hook,用于复制文本到剪贴板。
*
* @returns {Array} 包含单个函数 `handleCopy`,
用于复制文本到剪贴板
*/
const useCopyToClipboard = () => {
function handleCopy(text) {
if (text) {
let input = document.createElement('input');
input.type = 'text';
input.value = text;
input.style.position = 'fixed';
input.style.opacity = '0';
document.body.appendChild(input);
input.select();
if (document.execCommand('copy')) {
message.success('复制成功');
} else {
message.error('复制失败');
}
document.body.removeChild(input);
} else {
message.error('复制失败');
}
}
return [handleCopy];
};
export default useCopyToClipboard;
用法
import useCopyToClipboard from './useCopyToClipboard';
const [handleCopy] = useCopyToClipboard();
const copyText = () => {
handleCopy('需要复制的文本');
};
4、useSmoothEnter
代码
import { useState, useEffect, useRef } from 'react';
/**
* useSmoothEnter:一个 hook,在 DOM 元素进入视口时添加平滑的进入动画。
*
* @returns {object} ref - 一个可以附加到 DOM 元素的 ref 对象,用于动画效果
*/
const useSmoothEnter = () => {
const ref = useRef(null);
const [isVisible, setIsVisible] = useState(false);
const [animationStyle, setAnimationStyle] = useState({
animation: '',
animationPlayState: 'paused',
});
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
const [entry] = entries;
setIsVisible(entry.isIntersecting);
});
const element = ref.current;
if (element) {
observer.observe(element);
return () => {
observer.unobserve(element);
};
}
}, [ref]);
useEffect(() => {
if (isVisible) {
setAnimationStyle({
animation: 'enterAnimation 1s ease',
animationPlayState: 'running',
});
}
}, [isVisible]);
useEffect(() => {
const element = ref.current;
if (element) {
element.style.animation = animationStyle.animation;
element.style.animationPlayState = animationStyle.animationPlayState;
}
}, [animationStyle]);
return ref;
};
export default useSmoothEnter;
用法
import useSmoothEnter from './useSmoothEnter';
const ref = useSmoothEnter();
return <div ref={ref} className="animated-element">内容</div>;