美丽的公主和它的27个React 自定义 Hook(二)

简介: 美丽的公主和它的27个React 自定义 Hook(二)

3.5 useCookie

import { useState, useCallback } from "react";
import Cookies from "js-cookie";
type CookieHookReturn<T> = [
  T | null,
  (newValue: T, options?: Cookies.CookieAttributes) => void,
  () => void
];
export default function useCookie<T>(
  name: string,
  defaultValue: T
): CookieHookReturn<T> {
  const [value, setValue] = useState<T | null>(() => {
    const cookie = Cookies.get(name);
    if (cookie) return JSON.parse(JSON.stringify(cookie));
    Cookies.set(name, JSON.stringify(defaultValue));
    return defaultValue;
  });
  const updateCookie = useCallback(
    (newValue: T, options?: Cookies.CookieAttributes) => {
      Cookies.set(name, JSON.stringify(newValue), options);
      setValue(newValue);
    },
    [name]
  );
  const deleteCookie = useCallback(() => {
    Cookies.remove(name);
    setValue(null);
  }, [name]);
  return [value, updateCookie, deleteCookie];

在初始化时,useCookie会检索具有指定名称的Cookie值。

  • 如果Cookie存在,它将返回其值;
  • 否则,它将Cookie设置为提供的默认值。

这个自定义钩子的一个主要优点是能够更新Cookie值。由useCookie返回的updateCookie函数允许我们修改Cookie的值。通过使用新值和可选的选项(如过期时间或路径)调用此函数,我们可以立即更新Cookie。此外,该钩子方便地更新状态,使我们的应用程序与修改后的Cookie保持同步

在需要删除Cookie的情况下,deleteCookie函数就派上用场了。只需调用此函数,它将从浏览器中删除指定的Cookie。该钩子会负责更新状态,确保我们的应用程序反映了Cookie的删除。

使用场景

useCookie可以在各种情境中使用。在处理用户信息身份验证令牌或需要跨不同会话保持的数据时,它特别有用。

import useCookie from "@hooks/useCookie"
export default function CookieComponent() {
  const [value, update, remove] = useCookie<string>("name", "前端柒八九");
  return (
    <>
      <div>{value}</div>
      <button onClick={() => update("789")}>修改cookie</button>
      <button onClick={remove}>移除cookie</button>
    </>
  );

3.6 useCopyToClipboard

import { useState } from "react";
import copy from "copy-to-clipboard";
interface Options {
  debug?: boolean;
  message?: string;
  format?: string;
  onCopy?: (clipboardData: object) => void;
}
type CopyToClipboardHookReturn = [
  (text: string, options?: Options) => void,
  { value: string | null; success: boolean | null }
];
export default function useCopyToClipboard(): CopyToClipboardHookReturn {
  const [value, setValue] = useState<string | null>(null);
  const [success, setSuccess] = useState<boolean | null>(null);
  const copyToClipboard = (text: string, options?: Options) => {
    const result = copy(text, options);
    if (result) setValue(text);
    setSuccess(result);
  };
  return [copyToClipboard, { value, success }];
}

React文本复制是一个常见并且繁琐的事情。五星上将,麦克阿瑟说,我们需要一个自定义hook - useCopyToClipboard来简化这个过程。

useCopyToClipboard钩子利用了ReactuseState钩子,以及copy-to-clipboard库,以实现其功能。通过调用这个自定义钩子,我们可以获得两个关键功能:copyToClipboard和相应的状态变量

copyToClipboard函数接受两个参数:要复制的文本和可选的配置选项。

  • 当复制成功时,提供的文本将被设置为当前值,成功状态将设置为true
  • 相反,如果复制失败,成功状态将保持为false

使用场景

useCopyToClipboard钩子可以在各种情境中使用。它在需要复制文本,如URL、可分享内容或用户生成的数据的情况下特别有用。

import useCopyToClipboard from "@hooks/useCopyToClipboard";
import { useRef } from "react";
export default function CopyToClipboardComponent() {
  const [copyToClipboard, { success, value }] = useCopyToClipboard();
  const inputRef = useRef<HTMLInputElement>(null);
  return (
    <>
      <button onClick={() => copyToClipboard(String(inputRef.current?.value))}>
        {success ? "复制过了" : "未复制"}
      </button>
      <input type="text" ref={inputRef} />
      复制的值-{value}
    </>
  );
}

3.7 useStorage

import { useCallback, useState, useEffect } from "react";
export function useLocalStorage<T>(key: string, defaultValue: T | (() => T)) {
  return useStorage(key, defaultValue, window.localStorage);
}
export function useSessionStorage<T>(key: string, defaultValue: T | (() => T)) {
  return useStorage(key, defaultValue, window.sessionStorage);
}
function useStorage<T>(
  key: string,
  defaultValue: T | (() => T),
  storageObject: Storage
): [T, React.Dispatch<React.SetStateAction<T>>, () => void] {
  const [value, setValue] = useState<T>(() => {
    const value = storageObject.getItem(key);
    if (value != null) return JSON.parse(value);
    if (typeof defaultValue === "function") {
      const value = (defaultValue as () => T)();
      return value;
    } else {
      return JSON.parse(JSON.stringify(defaultValue));
    }
  });
  useEffect(() => {
    if (value === undefined) return storageObject.removeItem(key);
    storageObject.setItem(key, JSON.stringify(value));
  }, [key, value, storageObject]);
  const remove = useCallback(() => {
    setValue(undefined as unknown as T);
  }, []);
  return [value, setValue, remove];
}

useStorage钩子提供两个便捷的功能:useLocalStorageuseSessionStorage

  • useLocalStorage,我们可以轻松地在浏览器的本地存储中存储和检索数据,
  • useSessionStorage则提供了相同的功能,但是使用会话存储

我们可以使用它来存储任何类型的数据,如字符串、数字,甚至复杂对象。此外,useStorage为我们处理数据的序列化和反序列化,因此我们不必担心将值转换为JSON格式或从JSON格式还原。

另一个优点是存储数据与组件状态之间的自动同步。每当存储的数据发生更改时,该钩子会相应地更新组件的状态。同样,当组件的状态发生更改时,该钩子会自动将新值持久化到存储中。这种双向同步确保我们的应用程序始终反映最新的数据,使其非常适合需要实时更新的场景。

useStorage钩子还提供了一个remove函数,允许我们在不再需要存储的值时轻松删除它们。在实现注销按钮或清除特定用户数据等功能时,此功能非常有用。

使用场景

我们可以在各种场景中使用useStorage钩子。例如,假设我们有一个设置面板,用户可以在其中自定义其偏好设置。通过使用useLocalStorage,我们可以轻松存储和检索这些设置,确保它们在重新加载页面时保持不变,甚至在用户关闭并重新打开浏览器时也是如此。

import { useSessionStorage, useLocalStorage } from "@hooks/useStorage";
export default function StorageComponent() {
  const [info, setInfo, removeInfo] = useSessionStorage<{ name: string }>(
    "info",
    {
      name: "front789",
    }
  );
  const [age, setAge, removeAge] = useLocalStorage<number>("age", 26);
  return (
    <div>
      <div>
        {info?.name} -{age}
      </div>
      <button onClick={() => setInfo({ name: "范美丽" })}>修改名称</button>
      <button onClick={() => setAge(18)}>修改年龄</button>
      <button onClick={removeInfo}>删除名称</button>
      <button onClick={removeAge}>删除年龄</button>
    </div>
  );
}

3.8 useMediaQuery

import { useState, useEffect } from "react";
import useEventListener from "@hooks/useEventListener";
export default function useMediaQuery(mediaQuery: string)
: boolean {
  const [isMatch, setIsMatch] = useState<boolean>(false);
  const [mediaQueryList, setMediaQueryList] = 
        useState<MediaQueryList | null>(
          null
        );
  useEffect(() => {
    const list = window.matchMedia(mediaQuery);
    setMediaQueryList(list);
    setIsMatch(list.matches);
  }, [mediaQuery]);
  useEventListener(
    "change",
    (e) => setIsMatch((e as MediaQueryListEvent).matches),
    mediaQueryList
  );
  return isMatch;
}

useMediaQuery钩子允许我们根据给定的媒体查询动态更新用户界面。只需将所需的媒体查询作为参数传递,该钩子将返回一个布尔值,指示媒体查询是否与当前视口大小匹配。

使用该自定义钩子可以轻松地在整个应用程序中实现响应式行为。无论我们需要有条件地渲染组件、应用特定的样式,还是根据屏幕大小触发不同的功能,useMediaQuery都能满足我们的需求。

使用场景

这个钩子不仅限于特定的用例,它可以在各种场景中使用。例如,我们可以使用它动态调整导航菜单的布局,根据屏幕大小隐藏或显示某些元素,甚至可以根据可用空间优化数据的加载。useMediaQuery钩子赋予我们在不同设备和屏幕尺寸上提供提高用户体验的能力

import useMediaQuery from "@hooks/useMediaQuery"
export default function MediaQueryComponent() {
    // 传人媒体查询条件
    const isLarge = useMediaQuery("(min-width: 200px)")
    return <div>视口超过查询条件了: {isLarge.toString()}</div>
}

3.9 useDarkMode

import { useEffect } from "react";
import useMediaQuery from "@hooks/useMediaQuery";
import { useLocalStorage } from "@hooks/useStorage";
type UseDarkModeReturn = [boolean, (value: boolean) => void];
export default function useDarkMode(): UseDarkModeReturn {
  const [darkMode, setDarkMode] = useLocalStorage("useDarkMode", false);
  const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
  const enabled = darkMode ?? prefersDarkMode;
  useEffect(() => {
    document.body.classList.toggle("dark-mode", enabled);
  }, [enabled]);
  const toggleDarkMode = (value: boolean) => {
    setDarkMode(value);
  };
  return [enabled, toggleDarkMode];
}

这个自定义钩子结合了另外两个方便的钩子useMediaQueryuseStorage,以提供一个快速切换应用深色模式的功能。它自动检测用户的首选颜色方案,并将深色模式状态保留在浏览器的本地存储中。

useDarkMode钩子在启用深色模式时动态更新HTML body的类,以应用dark-mode样式。这种方法确保了在所有组件中的一致性,而无需手动进行类的操作。

body.css

body.dark-mode {
    background-color: #333;
}

我们可以在各种情境中使用useDarkMode钩子。无论我们是无论构建博客、还是电商平台,深色模式都可以提升用户体验,减轻眼部压力,并延长设备电池寿命。

使用场景

通过点击切换 Dark Mode按钮,我们可以立即在浅色和深色主题之间切换。按钮的外观会动态改变,反映当前的模式。

import useDarkMode from "@hooks/useDarkMode";
import "./body.css";
export default function DarkModeComponent() {
  const [darkMode, setDarkMode] = useDarkMode();
  return (
    <button
      onClick={() => setDarkMode(!darkMode)}
      style={{
        border: `1px solid ${darkMode ? "white" : "black"}`,
        background: "none",
        color: darkMode ? "white" : "black",
      }}
    >
      切换 Dark Mode
    </button>
  );
}


3.10 useTimeout

import { useCallback, useEffect, useRef } from "react";
type TimeoutHookReturn = {
  reset: () => void;
  clear: () => void;
};
export default function useTimeout(callback: () => void, delay: number): TimeoutHookReturn {
  const callbackRef = useRef<() => void>(callback);
  const timeoutRef = useRef<number | undefined>();
  useEffect(() => {
    callbackRef.current = callback;
  }, [callback]);
  const set = useCallback(() => {
    timeoutRef.current = setTimeout(() => callbackRef.current(), delay);
  }, [delay]);
  const clear = useCallback(() => {
    timeoutRef.current && clearTimeout(timeoutRef.current);
  }, []);
  useEffect(() => {
    set();
    return clear;
  }, [delay, set, clear]);
  const reset = useCallback(() => {
    clear();
    set();
  }, [clear, set]);
  return { reset, clear };
}

useTimeout钩子封装了在 React 组件中设置、清除和重置超时的逻辑。它接受两个参数:回调函数和延迟持续时间(以毫秒为单位)。每当指定的延迟时间过去时,将执行提供的回调函数。

这个自定义钩子的一个重要优点是,它确保即使在组件重新渲染期间更改,回调函数仍然保持最新状态。通过使用 useRef 来存储回调引用,该钩子保证始终调用最新版本的函数。

此外,useTimeout钩子通过使用 useCallback 来记忆 setclear 函数,优化了性能。这意味着只有在它们的依赖项更改时才重新创建这些函数,从而防止不必要的渲染,提高了效率。

使用场景

useTimeout 钩子可以在需要定时操作的各种场景中使用。例如,在倒计时组件中,以轻松地实现在特定持续时间后重置的计时器。

import { useState } from "react";
import useTimeout from "@hooks/useTimeout";
export default function TimeoutComponent() {
  const [count, setCount] = useState(10);
  const { clear, reset } = useTimeout(() => setCount(789), 1000);
  return (
    <div>
      <div>{count}</div>
      <button onClick={() => setCount((c) => c + 1)}>数据+1</button>
      <button onClick={clear}>清除定时器</button>
      <button onClick={reset}>设定回调函数,将数字设置为789</button>
    </div>
  );
}

3.11 useDebounce

import { useEffect, DependencyList } from "react";
import useTimeout from "@hooks/useTimeout";
export default function useDebounce(
  callback: () => void,
  delay: number,
  dependencies: DependencyList
) {
  const { reset, clear } = useTimeout(callback, delay);
  useEffect(reset, [...dependencies, reset]);
  useEffect(clear, [clear]);
}

useDebounce钩子内部利用useTimeout钩子来延迟执行回调函数,直到指定的延迟时间已过。通过这样做,它防止了由于快速输入更改或重复事件引起的频繁更新,从而实现更流畅的交互和减少资源消耗。

useDebounce通过将回调函数延迟持续时间以及任何依赖项包装在这个自定义钩子中,我们可以轻松实现防抖功能,而无需使组件代码混乱不堪。该钩子负责管理超时并在必要时清除它,确保仅在指定的延迟时间和最新的依赖项后触发回调。

使用场景

这个自定义钩子在需要处理用户输入的情况下特别有用,比如搜索栏或表单字段,我们希望延迟执行某个操作,直到用户完成输入或交互。它还可用于优化网络请求,确保仅在用户停止输入或选择选项后发送请求。

import { useState } from "react";
import useDebounce from "@hooks/useDebounce";
export default function DebounceComponent() {
  const [count, setCount] = useState(10);
  useDebounce(() => alert(`触发回掉,并获取最新的值${count}`), 1000, [count]);
  return (
    <div>
      <div>{count}</div>
      <button onClick={() => setCount((c) => c + 1)}>数字+1</button>
    </div>
  );
}

每当用户点击数字+1按钮时,计数状态会更新。但是,我们不会立即弹出计数值,而是使用useDebounce来防抖回调函数。只有在延迟1秒后,计数值才会弹出,有效地防止了在快速点击按钮时弹出过多的输出。


3.12 useToggle

import { useState } from "react";
export default function useToggle(defaultValue: boolean) {
  const [value, setValue] = useState(defaultValue);
  function toggleValue(value: boolean | undefined) {
    setValue((currentValue) =>
      typeof value === "boolean" ? value : !currentValue
    );
  }
  return [value, toggleValue] as const;
}

useToggle 只需一行代码,我们就可以使用默认值初始化状态。toggleValue 函数使我们能够轻松地在 truefalse 之间切换状态,或者我们可以直接传递一个布尔值来将状态设置为所需的值。这种多功能性使 useToggle 成为各种需要切换或改变状态的场景的理想选择。

使用场景

使用 useToggle 钩子来管理切换按钮的状态。通过简单的单击,按钮的状态在 truefalse 之间切换。此外,该钩子提供了按钮,允许直接将值设置为 truefalse,以满足特定用例。

import useToggle from "@hooks/useToggle";
export default function ToggleComponent() {
  const [value, toggleValue] = useToggle(false);
  return (
    <div>
      <div>{value.toString()}</div>
      <button onClick={() => toggleValue(!value)}>状态切换</button>
      <button onClick={() => toggleValue(true)}>直接设置为true</button>
      <button onClick={() => toggleValue(false)}>直接设置为false</button>
    </div>
  );
}
相关文章
|
3月前
|
自然语言处理 前端开发 JavaScript
说说你对 React Hook的闭包陷阱的理解,有哪些解决方案?
说说你对 React Hook的闭包陷阱的理解,有哪些解决方案?
28 0
|
3月前
|
自然语言处理 前端开发 JavaScript
美丽的公主和它的27个React 自定义 Hook(四)
美丽的公主和它的27个React 自定义 Hook(四)
|
3月前
|
存储 前端开发 数据可视化
美丽的公主和它的27个React 自定义 Hook(三)
美丽的公主和它的27个React 自定义 Hook(三)
|
3月前
|
前端开发 JavaScript API
美丽的公主和它的27个React 自定义 Hook(一)
美丽的公主和它的27个React 自定义 Hook(一)
|
3月前
|
前端开发
react hook 生命周期
react hook 生命周期
26 0
|
前端开发
React Native自定义导航栏
之前我们学习了可触摸组件和页面导航的使用的使用: 从零学React Native之09可触摸组件 … 从零学React Native之03页面导航 … 经过之前的学习, 我们可以完成一个自定义导航栏了, 效果如下: 我们需要创建一个 NaviBar.js 用来显示顶部的导航栏, 还需要四个界面(Page1.js,Page2.js,Page3.js,Page4.js
1526 0
|
2月前
|
设计模式 前端开发 数据可视化
【第4期】一文了解React UI 组件库
【第4期】一文了解React UI 组件库
52 0
|
2月前
|
存储 前端开发 JavaScript
【第34期】一文学会React组件传值
【第34期】一文学会React组件传值
21 0
|
2月前
|
前端开发
【第31期】一文学会用React Hooks组件编写组件
【第31期】一文学会用React Hooks组件编写组件
24 0
|
2月前
|
资源调度 前端开发 JavaScript
React 的antd-mobile 组件库,嵌套路由
React 的antd-mobile 组件库,嵌套路由
24 0

相关产品

  • 云迁移中心