React Hooks --- 分享自己开发中常用的自定义的Hooks (1)

时间:2024-07-06 16:21:27

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