為什么要使用自定義 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>;