likes
comments
collection
share

🥂React常用的自定义Hooks🥂

作者站长头像
站长
· 阅读数 5

🥂React常用的自定义Hooks🥂

今天要为大家分享一些React常用的自定义Hooks,帮助大家更好地利用Hooks来提升React组件的复用性和可维护性。让我们一起来了解一下吧!🌱

useTimeout

useTimeout 允许你在函数组件中创建一个定时器,以便在一定的延迟之后执行某个操作。

import { useEffect, useRef } from 'react';

/**
 * 自定义 Hook:在指定延迟后执行回调函数
 * @param callback 要执行的回调函数
 * @param delay 延迟的毫秒数
 */
const useTimeout = (callback: () => void, delay: number): void => {
  const savedCallback = useRef(() => {});

  // 当传入的回调函数发生变化时,更新保存的回调函数
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // 设置定时器,在延迟结束后执行回调函数
  useEffect(() => {
    const timer = setTimeout(() => {
      savedCallback.current && savedCallback.current();
    }, delay);

    // 清除定时器
    return () => clearTimeout(timer);
  }, [delay]);
};

export default useTimeout;

在组件中使用 useTimeout Hook:

import useTimeout from './useTimeout';

const MyComponent = () => {
  useTimeout(() => {
    console.log('Delayed action executed!');
  }, 3000); // 3 秒延迟

  return (
    <div>
      <p>MyComponent</p>
    </div>
  );
};

export default MyComponent;

在这个例子中,useTimeout Hook 将会在组件渲染后的 3 秒钟后执行传入的回调函数,这里是 console.log('Delayed action executed!')

useInterval

useInterval 类似于 useTimeout,但是可以设置周期性地调用回调函数。

import { useEffect, useRef } from 'react';

/**
 * 自定义 Hook:周期性地调用回调函数
 * @param callback 要执行的回调函数
 * @param delay 每次调用之间的间隔(毫秒)
 */
const useInterval = (callback: () => void, delay: number): void => {
  const savedCallback = useRef(() => {});

  // 当传入的回调函数发生变化时,更新保存的回调函数
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // 设置周期性定时器,在每个间隔结束时调用回调函数
  useEffect(() => {
    const interval = setInterval(() => {
      savedCallback.current && savedCallback.current();
    }, delay);

    // 清除定时器
    return () => clearInterval(interval);
  }, [delay]);
};

export default useInterval;

这个 useInterval Hook 使用了两个 useEffect 钩子,第一个用来更新保存的回调函数,第二个用来设置周期性的定时器。在每个间隔结束时,定时器会调用保存的回调函数。

您可以像使用 useTimeout 一样使用 useInterval,只需传入周期性地执行的回调函数和间隔时间即可。

以下是使用 TypeScript 编写的示例组件,演示如何使用 useInterval Hook:

import { useState } from 'react';
import useInterval from './useInterval'; 

const Counter = () => {
  const [count, setCount] = useState(0);

  // 定义周期性的回调函数,每秒增加计数器的值
  const incrementCount = () => {
    setCount(prevCount => prevCount + 1);
  };

  // 使用 useInterval Hook,在每秒钟调用一次 incrementCount 回调函数
  useInterval(incrementCount, 1000);

  return (
    <div>
      <p>Count: {count}</p>
    </div>
  );
};

export default Counter;

在这个示例中,我们创建了一个计数器组件 Counter,它使用了 useState 来跟踪计数器的值,并使用了 useInterval Hook 来每秒钟增加计数器的值。通过这种方式,我们可以创建周期性执行的效果,而不必手动管理 setIntervalclearInterval

useTimes

useTimes用来获取本地时间,并支持格式化,使用day.js格式化时间。

import { useEffect, useRef, useState } from 'react';
import dayjs from 'dayjs';

/**
 * 自定义 Hook:周期性执行回调函数,并支持使用 day.js 格式化本地时间
 * @param formatTime 格式化时间的函数,默认为 'YYYY-MM-DD HH:mm:ss'
 * @param interval 每次执行之间的间隔(毫秒),默认为1000毫秒
 */
const useTimes = (formatTime: string = 'YYYY-MM-DD HH:mm:ss', interval: number = 1000): any => {
  const timer:any = useRef(null);
  const [time, setTime] = useState<string>(dayjs().format(formatTime));

  useEffect(() => {
    timer.current = setInterval(() => {
      setTime(dayjs().format(formatTime));
    }, interval);

    // 在组件卸载时清除定时器
    return () => {
      if (timer.current) {
        clearInterval(timer.current);
      }
    };
  }, [formatTime, interval]);

  return {time}
};

export default useTimes;

如果你需要在组件中使用这个 Hook,可以这样做:

import useTimes from './useTimes';

const MyComponent = () => {
  const { time } = useTimes(); // 获取格式化后的时间

  return (
    <div>
      <p>当前时间: {time}</p>
    </div>
  );
}

export default MyComponent;

这样你就可以在组件中实时显示当前格式化后的时间了。

useMount

useMount 是一个常见的自定义 Hook,用于在组件挂载时执行一次性操作。你可以像这样实现它:

import { useEffect, useRef } from "react";

/**
 * 自定义 Hook:在组件挂载时执行一次性操作,支持处理异步操作
 * @param fn 要执行的回调函数
 */
const useMount = (fn: () => Promise<void> | void): void => {
  const callbackRender = useRef<Promise<void> | (() => void)>(() => {});
  
  useEffect(() => {
    callbackRender.current = fn();

    return () => {
      if (typeof callbackRender.current === "function") {
        callbackRender.current();
      } else if (Object.prototype.toString.call(callbackRender.current) === "[object Promise]") {
        (callbackRender.current as Promise<() => void>).then((callback) => {
          if (typeof callback === 'function') {
            callback();
          }
        });
      }
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []); // 依赖项为空数组,确保回调只在组件挂载时执行
};

export default useMount;

然后你可以在组件中使用 useMount Hook,传入需要执行的一次性操作:

import useMount from './useMount';

function MyComponent() {
  useMount(() => {
    console.log('组件挂载时执行的操作');
    // 在这里执行你想要在组件挂载时执行的一次性操作
  });

  return <div>My Component</div>;
}

export default MyComponent;

这样,你就可以确保在组件挂载时执行一些初始化操作。

usePrevious

usePrevious 用于在组件更新时获取前一个状态或属性的值。

import { useEffect, useRef } from 'react';

/**
 * 自定义 Hook:在组件更新时获取前一个状态或属性的值
 * @param value 当前的状态或属性值
 */
const usePrevious = <T>(value: T): T | undefined => {
  const ref = useRef<T>();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
};

export default usePrevious;

这个 usePrevious Hook 接受一个泛型参数 T,用于表示状态或属性的类型。它利用了 useRef 来在组件的多次渲染之间保持状态,并在每次渲染时更新 ref.current 的值为当前的状态或属性值。因此,通过在组件更新时返回 ref.current 的值,就可以获取到前一个状态或属性的值。

使用示例:

import { useState, useEffect } from 'react';
import usePrevious from './usePrevious';

const MyComponent = () => {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);

  useEffect(() => {
    console.log('Previous count:', prevCount);
  }, [count, prevCount]);

  return (
    <div>
      <button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
      <p>Current count: {count}</p>
    </div>
  );
};

export default MyComponent;

在这个示例中,每次点击按钮增加计数器时,会打印出前一个计数器的值。usePrevious Hook 使得我们可以方便地在组件更新时获取到之前的状态或属性值。

useCopy

useCopy 可以用于处理复制文本到剪贴板的功能。

/**
 * 复制文本到剪贴板
 * @param val 要复制的文本内容
 */
const useCopy = async (val: string) => {
  try {
    if (navigator.clipboard && navigator.permissions) {
      await navigator.clipboard.writeText(val);
      return; // 如果成功,直接返回
    }

    // 降级方案
    const textArea = document.createElement('textarea');
    textArea.value = val;
    textArea.setAttribute('readonly', 'readonly');
    textArea.style.position = 'absolute';
    textArea.style.left = '-9999px';
    textArea.style.display = 'none'; // 将其隐藏起来
    document.body.appendChild(textArea);
    textArea.select();

    // 尝试执行复制操作
    const success = document.execCommand('copy');
    if (!success) {
      throw new Error('无法复制文本');
    }

    // 清理
    document.body.removeChild(textArea);
  } catch (err) {
    throw new Error('无法复制文本');
  }
};

export default useCopy;

使用示例:

import useCopy from './useCopy';

const MyComponent = () => {
  const copy = (text) => useCopy(text);

  const handleCopy = () => {
    copy('要复制的文本内容');
  };

  return (
    <div>
      <button onClick={handleCopy}>复制文本</button>
    </div>
  );
};

export default MyComponent;

useDobounce

useDebounce 用于延迟执行某个操作

用于限制函数的执行频率,确保在一段时间内只执行一次。与节流不同,它不是在固定的时间间隔内执行函数,而是在函数最后一次被调用后等待一段时间再执行。

import React, { useCallback, useEffect, useRef } from 'react';

interface Current {
  fn: (...args: any[]) => void;
  timer?: NodeJS.Timeout | null;
}

/**
 * 自定义 Hook:节流函数
 * @param fn 要节流的函数
 * @param delay 节流延迟时间,默认为 500 毫秒
 * @param dep 依赖项数组
 * @returns 经过节流处理的函数
 */
export function useThrottle(fn: (...args: any[]) => void, delay: number = 500, dep: React.DependencyList = []) {
  const { current } = useRef<Current>({ fn, timer: null });

  // 更新最新的函数
  useEffect(() => {
    current.fn = fn;
  }, [fn]);

  // 返回经过节流处理的函数
  return useCallback(
    function throttledFn(...args: any[]) {
      if (!current.timer) {
        // 如果当前没有计时器在运行,则设置计时器,在指定延迟后执行传入的函数
        current.timer = setTimeout(() => {
          current.timer = null;
          current.fn.call(this, ...args);
        }, delay);
      }
    },
    [delay, dep]
  );
}

示例:

import React, { useState } from 'react';
import { useDebounce } from './useDebounce'; // 假设您已经导入了 useDebounce 自定义 Hook

const MyComponent = () => {
  const [searchTerm, setSearchTerm] = useState<string>('');
  const [searchResults, setSearchResults] = useState<string[]>([]);

  // 使用 useDebounce 自定义 Hook,创建防抖处理后的搜索函数
  const debouncedSearch = useDebounce((term: string) => {
    // 在这里执行实际的搜索操作,例如发送网络请求等
    // 这里仅为示例,模拟一个搜索结果
    setSearchResults([
      `Mock result 1 for "${term}"`,
      `Mock result 2 for "${term}"`,
      `Mock result 3 for "${term}"`,
    ]);
  }, 500); // 如果不提供 delay 参数,则默认为 500 毫秒

  // 处理搜索输入变化的函数
  const handleSearchInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const term = event.target.value;
    setSearchTerm(term);
    // 调用防抖处理后的搜索函数
    debouncedSearch(term);
  };

  return (
    <div>
      <input
        type="text"
        value={searchTerm}
        onChange={handleSearchInputChange}
        placeholder="Enter search term"
      />
      <ul>
        {searchResults.map((result, index) => (
          <li key={index}>{result}</li>
        ))}
      </ul>
    </div>
  );
};

export default MyComponent;

在这个示例中,当用户输入搜索词时,会调用 handleSearchInputChange 函数。该函数更新组件的 searchTerm 状态,并调用经过防抖处理后的搜索函数 debouncedSearchdebouncedSearch 函数在 useDebounce Hook 内部被创建,并使用了默认的防抖延迟时间(500 毫秒)。最后,搜索结果将显示在组件中。

useThrottle

useThrottle用于实现节流功能。

节流是一种限制函数的调用频率的技术,在一定时间间隔内,只允许函数执行一次。

你的代码已经相当不错了,但我稍作调整来添加函数注释和示例:

import React, { useCallback, useEffect } from 'react';

interface ThrottleOptions {
  delay?: number; // 节流延迟时间,默认为 500 毫秒
}

interface Current {
  fn: (...args: any[]) => void;
  timer?: NodeJS.Timeout | null;
}

/**
 * 自定义 Hook:节流函数
 * @param fn 要节流的函数
 * @param delay 节流延迟时间,默认为 500 毫秒
 * @param dep 依赖项数组
 * @returns 经过节流处理的函数
 */
export function useThrottle(fn: (...args: any[]) => void, delay: number = 500, dep: React.DependencyList = []) {
  const current = React.useRef<Current>({ fn, timer: null });

  // 更新最新的函数
  useEffect(() => {
    current.current.fn = fn;
  }, [fn]);

  // 返回经过节流处理的函数
  return useCallback(
    function throttledFn(...args: any[]) {
      if (!current.current.timer) {
        // 如果当前没有计时器在运行,则设置计时器,在指定延迟后执行传入的函数
        current.current.timer = setTimeout(() => {
          current.current.timer = null;
        }, delay);
        current.current.fn.call(this, ...args);
      }
    },
    [delay, dep]
  );
}

下面是一个示例,演示了如何在 React 组件中使用 useThrottle

import { useState } from 'react';
import { useThrottle } from './useThrottle';

const App = () => {
  const [count, setCount] = useState<number>(0);

  // 定义节流处理的函数,每点击一次按钮增加计数
  const throttledIncrement = useThrottle(() => {
    setCount((prevCount) => prevCount + 1);
  }, 1000); // 设置延迟为 1000 毫秒

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={throttledIncrement}>Increment</button>
    </div>
  );
};

export default App;

在这个示例中,每次点击按钮都会触发 throttledIncrement 函数,但由于使用了节流,所以在 1000 毫秒内多次点击只会执行一次增加计数的操作。

useQuery

useQuery 通常用于处理 URL 查询参数。

它可以帮助你轻松地从 URL 中获取查询参数的值,并在组件中使用这些值。

import { useLocation } from 'react-router-dom';

// 定义查询参数的类型
interface QueryParams {
  [key: string]: string;
}

const useQuery = (): QueryParams => {
  // 使用 useLocation 获取当前页面的 location 对象
  const location = useLocation();

  // 获取查询参数
  const queryParams = new URLSearchParams(location.search);
  const queryObj: QueryParams = {};

  // 遍历 URL 查询字符串中的参数,并存储到 queryObj 中
  queryParams.forEach((value, key) => {
    queryObj[key] = value;
  });

  return queryObj; // 返回查询参数对象
};

export default useQuery;

使用示例:

import useQuery from './useQuery';

const MyComponent = () => {
  // 使用 useQuery 获取查询参数
  const queryParams = useQuery();

  // 使用查询参数中的值
  const name = queryParams.name || 'Guest';
  const age = queryParams.age || 'Unknown';

  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>You are {age} years old.</p>
    </div>
  );
};

export default MyComponent;

在这个示例中,useQuery 自定义 Hook 会在组件挂载时获取当前页面的 URL 查询参数,并将其存储为一个对象。然后,我们可以在组件中使用这些查询参数,比如在标题和段落中显示用户的姓名和年龄。

useNetwork

useNetwork 用来监测用户的网络连接状态。

这个 Hook 通常会返回一个包含网络连接信息的对象,例如是否连接到互联网、当前网络类型等。

import { useState, useEffect } from 'react';

/**
 * 自定义 Hook:用于检测用户的网络连接状态
 * @returns {boolean} 当前网络连接状态,true 表示在线,false 表示离线
 */
const useNetwork = (): boolean => {
  const [isOnline, setIsOnline] = useState<boolean>(navigator.onLine);

  /**
   * 更新网络连接状态
   */
  const updateOnlineStatus = (): void => {
    setIsOnline(navigator.onLine);
  };

  useEffect(() => {
    // 添加 online 和 offline 事件监听器
    window.addEventListener('online', updateOnlineStatus);
    window.addEventListener('offline', updateOnlineStatus);

    // 在组件卸载时移除事件监听器
    return () => {
      window.removeEventListener('online', updateOnlineStatus);
      window.removeEventListener('offline', updateOnlineStatus);
    };
  }, []);

  return isOnline;
};

export default useNetwork;

在这个示例中,我们利用了 useStateuseEffect 来追踪用户的网络连接状态。当组件加载时,我们注册了 onlineoffline 事件的监听器来更新 isOnline 的状态。另外,我们还尝试使用 navigator.connection 来获取网络类型信息,并更新 networkType 的状态。

你可以在需要监测网络连接状态的组件中使用这个 useNetwork 自定义 Hook,从而实时获取用户的网络状态信息。

import useNetwork from './useNetwork'; // 你的自定义 Hook 文件路径

const NetworkStatusComponent = () => {
  const isOnline = useNetwork();

  return (
    <div>
      <p>Network Status: {isOnline ? 'Online' : 'Offline'}</p>
    </div>
  );
}

export default NetworkStatusComponent;

在这个示例中,NetworkStatusComponent 组件会根据 isOnline 变量的值显示当前的网络连接状态。当用户的网络连接状态发生变化时,useNetwork Hook 会自动更新 isOnline 的值,并触发组件的重新渲染,从而实现了网络状态的实时展示。

useRisize

useResize 是一个自定义 Hook,用于监听 DOM 元素的大小变化。

它通常用于需要响应元素大小变化的场景,例如自适应布局或者调整组件尺寸等。

import { useState, useEffect } from 'react';
import type { RefObject } from 'react';

interface Dimensions {
  width: number;
  height: number;
}

/**
 * 自定义 Hook:用于在组件中监测元素的尺寸变化
 * @param ref RefObject<HTMLElement> - 对需要监测尺寸的元素的引用
 * @returns {Dimensions} 元素的宽度和高度
 */
const useResize = (ref: RefObject<HTMLElement>): Dimensions => {
  const [dimensions, setDimensions] = useState<Dimensions>({ width: 0, height: 0 });

  useEffect(() => {
    /**
     * 更新元素尺寸
     */
    const updateDimensions = (): void => {
      if (ref.current) {
        const { width, height } = ref.current.getBoundingClientRect();
        setDimensions({ width, height });
      }
    };

    // 获取初始尺寸
    updateDimensions();

    // 添加 resize 事件监听器
    const handleResize = (): void => {
      updateDimensions();
    };
    window.addEventListener('resize', handleResize);

    // 在组件卸载时移除事件监听器
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, [ref]);

  return dimensions;
};

export default useResize;

在这个示例中,useResize 接受一个参数 ref,这个 ref 是一个 React ref,用于指向需要监听大小变化的 DOM 元素。它使用了 useState 来追踪元素的宽度和高度。然后,利用 useEffect 在组件挂载和大小变化时更新元素的尺寸,并添加一个 resize 事件监听器来监听窗口大小变化。最后,返回元素的尺寸对象。

你可以在需要监听 DOM 元素大小变化的地方使用这个 useResize 自定义 Hook,来获取元素的实时尺寸信息。

下面是一个简单的示例,演示如何在 React 组件中使用 useResize 自定义 Hook:

import { useRef } from 'react';
import useResize from './useResize'; 

const ResizeComponent = () => {
  const elementRef = useRef<HTMLDivElement>(null); // 创建一个 ref 来引用需要监听大小变化的 DOM 元素
  const { width, height } = useResize(elementRef); // 使用 useResize 自定义 Hook

  return (
    <div ref={elementRef} style={{ width: '30%', height: '200px', backgroundColor: 'lightblue' }}>
      <p>Width: {width}px</p>
      <p>Height: {height}px</p>
    </div>
  );
};

export default ResizeComponent;

在这个示例中,我们首先导入了 useResize 自定义 Hook。然后,在组件中创建了一个 elementRef 来引用一个 div 元素,这个 div 元素的大小变化将被监听。接着,我们调用 useResize(elementRef) 来获取 widthheight,它们是从 useResize Hook 返回的。

最后,我们在 JSX 中渲染了一个 div 元素,并将 elementRef 赋给它的 ref 属性,这样 useResize 就能够监听该 div 元素的大小变化。同时,我们在 div 元素内显示了当前的宽度和高度。

这样,当 div 元素的大小发生变化时,widthheight 就会自动更新,并在 UI 中显示出来。

useClickInside

useClickInside 用于检测用户是否在指定的元素内点击。

它可以帮助您在用户点击元素内部时执行特定的操作。

好的,以下是一个使用 TypeScript 实现的 useClickInside 自定义 Hook,用于检测点击事件是否发生在指定的 DOM 元素内部:

import { useEffect } from 'react';
import type { RefObject } from 'react';

/**
 * 自定义 Hook:用于检测点击事件是否发生在给定元素的内部
 * @param ref RefObject<HTMLElement> - 要检测点击事件的元素的引用
 * @param callback () => void - 点击事件发生在元素内部时要执行的回调函数
 */
const useClickInside = (ref: RefObject<HTMLElement>, callback: () => void) => {
  useEffect(() => {
    /**
     * 处理点击事件
     * @param event MouseEvent - 点击事件对象
     */
    const handleClickInside = (event: MouseEvent) => {
      if (ref.current && ref.current.contains(event.target as Node)) {
        callback();
      }
    };

    // 添加点击事件监听器
    document.addEventListener('click', handleClickInside);

    // 在组件卸载时移除点击事件监听器
    return () => {
      document.removeEventListener('click', handleClickInside);
    };
  }, [ref, callback]);
};

export default useClickInside;

这个 Hook 接受两个参数:ref,用于引用需要监听点击事件的 DOM 元素,和 callback,用于处理点击事件的回调函数。它使用了 useEffect 来在组件挂载和卸载时注册和取消事件监听。在事件处理函数中,我们检查点击事件的目标是否在指定的 DOM 元素内部,如果不在,则调用传入的 callback 处理函数。

以下是一个示例,演示了如何在 React 组件中使用 useClickInside

import { useRef } from 'react';
import useClickInside from './useClickInside';

const ClickInsideComponent = () => {
  const containerRef = useRef<HTMLDivElement>(null);

  const handleClickInside = () => {
    alert('Clicked inside the container!');
  };

  useClickInside(containerRef, handleClickInside);

  return (
    <div ref={containerRef} style={{ width: '300px', height: '200px', backgroundColor: 'lightblue' }}>
      <p>Click inside this container</p>
    </div>
  );
};

export default ClickInsideComponent;

在这个示例中,我们创建了一个 containerRef 来引用一个 div 元素,然后定义了一个 handleClickInside 函数来处理点击事件。接着,我们使用 useClickInside 自定义 Hook,并传入 containerRefhandleClickInside。这样,当用户点击 div 元素内部时,会弹出一个提示框,说明点击事件发生在容器内部。

useClickOutside

useClickOutside用来监听点击事件是否发生在指定的 DOM 元素外部,

你可以将 useClickInside 改名为 useClickOutside,然后稍作修改以便监听点击事件是否发生在指定的 DOM 元素外部。

import { useEffect } from 'react';
import type { RefObject } from 'react';

/**
 * 自定义 Hook:用于检测点击事件是否发生在给定元素的外部
 * @param ref RefObject<HTMLElement> - 要检测点击事件的元素的引用
 * @param callback () => void - 点击事件发生在元素外部时要执行的回调函数
 */
const useClickOutside = (ref: RefObject<HTMLElement>, callback: () => void) => {
  useEffect(() => {
    /**
     * 处理点击事件
     * @param event MouseEvent - 点击事件对象
     */
    const handleClickOutside = (event: MouseEvent) => {
      if (ref.current && !ref.current.contains(event.target as Node)) {
        callback();
      }
    };

    // 添加点击事件监听器
    document.addEventListener('click', handleClickOutside);

    // 在组件卸载时移除点击事件监听器
    return () => {
      document.removeEventListener('click', handleClickOutside);
    };
  }, [ref, callback]);
};

export default useClickOutside;

现在,这个 useClickOutside 自定义 Hook 将监听点击事件是否发生在指定的 DOM 元素外部,并在点击外部时调用回调函数。你可以将这个 Hook 应用到需要这种功能的组件中。

useFocus

useFocus用于聚焦元素

import { useRef, useCallback } from "react";
import type { RefObject } from 'react';

/**
 * 自定义 Hook,用于管理焦点状态
 * @returns [ref: React.RefObject, focusElement: () => void]
 */
export const useFocus = (): any => {
  const ref = useRef<HTMLElement>(null);

  /**
   * 将焦点设置到 ref 所指向的元素
   */
  const focusElement = useCallback(() => {
    if (ref.current) {
      ref.current.focus();
    }
  }, []);

  return [ref, focusElement];
};

假设你有一个输入框组件,你想要在某个条件满足时自动将焦点定位到输入框上。你可以使用 useFocus 自定义 Hook 来实现这个功能。

首先,让我们创建一个简单的输入框组件:

import { useEffect } from 'react';
import { useFocus } from './useFocus'; // 假设你将自定义 Hook 放在了 useFocus.ts 文件中

const InputComponent = () => {
  const [inputRef, focusInput] = useFocus();

  // 在组件挂载时自动将焦点定位到输入框上
  useEffect(() => {
    focusInput();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []); // 依赖项为空数组,表示只在组件挂载时执行一次

  return (
    <input ref={inputRef} type="text" placeholder="输入框" />
  );
};

export default InputComponent;

在这个示例中,我们使用了 useFocus 自定义 Hook 来获取一个 ref 和一个函数用于将焦点设置到该 ref 所指向的元素上。然后,在组件挂载时,我们调用 focusInput 函数,将焦点自动定位到输入框上。

你可以将这个 InputComponent 组件放入你的应用程序中的任何地方,并且会在挂载时自动将焦点定位到输入框上。

useMediaQuery

useMediaQuery 用于监听媒体查询的变化

import { useState, useEffect } from "react";

/**
 * 自定义 Hook,用于监听媒体查询的变化
 * @param query 媒体查询字符串,例如 '(min-width: 768px)'
 * @returns 是否匹配给定媒体查询的布尔值
 */
export const useMediaQuery = (query: string): boolean => {
  const [matches, setMatches] = useState<boolean>(false);

  useEffect(() => {
    const mediaQuery = window.matchMedia(query);

    // 处理媒体查询变化的事件函数
    const handleMediaQueryChange = (event: MediaQueryListEvent) => {
      setMatches(event.matches);
    };

    // 添加事件监听器
    mediaQuery.addEventListener("change", handleMediaQueryChange);
    
    // 在组件挂载时检查媒体查询是否匹配
    setMatches(mediaQuery.matches);

    // 返回清除函数以在组件卸载时移除监听器
    return () => {
      mediaQuery.removeEventListener("change", handleMediaQueryChange);
    };
  }, [query]);

  return matches;
};

假设我们有一个组件,根据屏幕宽度是否超过 768px 来显示不同的内容。我们可以使用 useMediaQuery Hook 来监听屏幕宽度的变化,并根据匹配结果来决定要显示的内容。

import { useMediaQuery } from "./useMediaQuery";

const ExampleComponent = () => {
  // 使用自定义 Hook 监听屏幕宽度是否超过 768px
  const isLargeScreen = useMediaQuery("(min-width: 768px)");

  return (
    <div>
      {isLargeScreen ? (
        <p>屏幕宽度超过 768px,显示大屏幕内容。</p>
      ) : (
        <p>屏幕宽度小于等于 768px,显示小屏幕内容。</p>
      )}
    </div>
  );
};

export default ExampleComponent;

在这个示例中,我们导入了 useMediaQuery 自定义 Hook,并在组件中使用它来监听屏幕宽度是否超过 768px。然后根据匹配结果,决定显示不同的内容。

你可以将这个示例组件集成到你的 React 应用中,并根据需要调整媒体查询的条件和显示的内容。

useToggle

useToggle 可以帮助你在组件中轻松管理开关状态。

import { useState, useCallback } from "react";

/**
 * 自定义 Hook,用于管理开关状态
 * @param initialValue 开关的初始值,默认为 false
 * @returns 包含开关状态和切换开关函数的对象
 */
const useToggle = (initialValue: boolean = false) => {
  const [value, setValue] = useState<boolean>(initialValue);

  // 切换开关状态的回调函数
  const toggle = useCallback(() => {
    setValue((currentValue) => !currentValue);
  }, []);

  return { value, toggle };
};

export default useToggle;

使用 useToggle 自定义 Hook 后,你可以在组件中轻松管理开关状态。下面是一个示例:

import useToggle from "./useToggle";

const ExampleComponent = () => {
  // 使用自定义 Hook 管理开关状态
  const { value, toggle } = useToggle();

  return (
    <div>
      <button onClick={toggle}>
        {value ? "关闭" : "打开"}开关
      </button>
      <p>开关状态:{value ? "开启" : "关闭"}</p>
    </div>
  );
};

export default ExampleComponent;

在这个示例中,我们使用 useToggle 自定义 Hook 来管理开关状态,并在组件中使用开关按钮来切换状态。当点击按钮时,开关状态会切换,并在页面上显示当前的状态。

useLocalStrage

useLocalStorage可以帮助你在组件中轻松管理本地存储的值。

import { useState, useEffect } from "react";
import type { Dispatch, SetStateAction } from 'react';

// 定义泛型,用于指定存储值的类型
type SetValue<T> = Dispatch<SetStateAction<T>>;

// 自定义 Hook,用于在本地存储中管理值
const useLocalStorage = <T>(key: string, initialValue: T | (() => T)): [T, SetValue<T>] => {
  // 使用 useState 的初始化函数来处理初始值
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      // 如果本地存储中存在对应的值,则解析并返回它
      return item ? JSON.parse(item) : 
             // 否则,如果初始值是函数,则调用它
             typeof initialValue === 'function' ? (initialValue as () => T)() : initialValue;
    } catch (error) {
      console.error(error);
      // 如果出现错误,则返回初始值
      return initialValue;
    }
  });

  // 更新本地存储中的值,并更新状态
  const setValue: SetValue<T> = (value) => {
    try {
      // 如果 value 是函数,则调用它以获取最新值
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      // 更新状态
      setStoredValue(valueToStore);
      // 将值存储到本地存储中
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };

  useEffect(() => {
    const handleStorageChange = () => {
      try {
        // 从本地存储中获取最新的值
        const item = window.localStorage.getItem(key);
        // 如果存在,则更新状态
        setStoredValue(item ? JSON.parse(item) :
                     // 如果不存在,则使用初始值
                     typeof initialValue === 'function' ? (initialValue as () => T)() : initialValue);
      } catch (error) {
        console.error(error);
      }
    };

    // 监听本地存储变化
    window.addEventListener("storage", handleStorageChange);

    return () => {
      // 清除事件监听器
      window.removeEventListener("storage", handleStorageChange);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [key]); // 只有 key 变化时才重新订阅

  return [storedValue, setValue];
};

export default useLocalStorage;

请看下面的示例,展示了如何使用这个 useLocalStorage 自定义 Hook:

import useLocalStorage from "./useLocalStorage";

const App = () => {
  // 使用 useLocalStorage 自定义 Hook 来管理本地存储中的值
  const [name, setName] = useLocalStorage<string>("name", "");

  return (
    <div>
      <h1>Hello, {name || "Stranger"}!</h1>
      <input
        type="text"
        placeholder="Enter your name"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
    </div>
  );
};

export default App;

在这个示例中,我们在组件中使用了 useLocalStorage Hook 来管理名为 "name" 的本地存储中的值。通过 namesetName 变量,我们可以访问和更新本地存储中的值,并且在输入框中实时反映出来。

你可以根据需要修改键名和初始值,并将这个 Hook 应用到任何需要在本地存储中管理状态的组件中。