使用自定义 Hook 增强您的 React 项目

avatar
前端工程师 @木狐

在这篇文章中,我们将深入探讨自定义 React 钩子,并探索它们在强化您的工作项目中所具备的令人难以置信的潜力。我亲自在自己的工作项目中使用了超过 20 个精心打造的钩子,现在我很高兴与您分享它们。从增强功能到简化工作流程,这些自定义钩子旨在赋予开发人员更大的能力,提供用户友好的体验。加入我们,一起探索这20多个钩子的威力,解锁您的 React 项目中的新水平,提高生产力和创新能力。

Github:  https: //github.com/sergeyleschev/react-custom-hooks

React Hooks 是在 React 16.8 版本中引入的一项功能,彻底改变了开发者在函数组件中编写和管理有状态逻辑的方式。在此之前,有状态逻辑只能在类组件中使用生命周期方法来实现。然而,有了 React Hooks,开发者现在可以直接在函数组件中使用状态和其他 React 特性。Hooks 提供了一种简便的方式,可以在多个组件之间轻松地重用有状态逻辑,提高了代码的可重用性并降低了复杂性。它们使开发者能够将复杂组件拆分成更小、更易管理的部分,从而产生更清晰、更易维护的代码。像 useState 和 useEffect 这样的 Hooks 允许开发者轻松地管理组件状态并处理副作用。凭借其简单性和灵活性,React Hooks 已成为构建现代、高效和可扩展的 React 应用的重要工具。

React 自定义 Hooks 是可重用的函数,允许开发者以可重用的方式抽象和封装复杂逻辑。自定义 Hooks 是通过组合现有的 React Hooks 或其他自定义 Hooks 创建的。它们使开发者能够从组件中提取通用逻辑并在应用程序的不同部分共享它。自定义 Hooks 遵循使用“use”前缀的命名约定,这使它们能够充分利用 React 的 Hooks 规则的好处。通过创建自定义 Hooks,开发者可以模块化和组织他们的代码,使其更易阅读、易维护和易测试。这些 Hooks 可以封装任何类型的逻辑,如 API 调用、表单处理、状态管理,甚至是抽象化外部库。React 自定义 Hooks 是一种强大的工具,促进了代码的可重用性并减少了重复,使开发更加高效和可扩展。

React Custom Hooks @ 2023,S. Leschev。谷歌工程级别:L6+

  • useArray
  • useAsync
  • useClickOutside
  • useCookie
  • useCopyToClipboard
  • useDarkMode
  • useDebounce
  • useDebugInformation
  • useDeepCompareEffect
  • useEffectOnce
  • useEventListener
  • useFetch
  • useGeolocation
  • useHover
  • useLongPress
  • useMediaQuery
  • useOnlineStatus
  • useOnScreen
  • usePrevious
  • useRenderCount
  • useScript
  • useStateWithHistory
  • useStateWithValidation
  • useStorage
  • useTimeout
  • useToggle
  • useTranslation
  • useUpdateEffect
  • useWindowSize

1. 使用数组

来源:  https: //github.com/sergeyleschev/react-custom-hooks


import { useState } from "react"

export default function useArray(defaultValue) {
    const [array, setArray] = useState(defaultValue)

    function push(element) {
        setArray(a => [...a, element])
    }

    function filter(callback) {
        setArray(a => a.filter(callback))
    }

    function update(index, newElement) {
        setArray(a => [
            ...a.slice(0, index),
            newElement,
            ...a.slice(index + 1, a.length),
        ])
    }

    function remove(index) {
        setArray(a => [...a.slice(0, index), ...a.slice(index + 1, a.length)])
    }

    function clear() {
        setArray([])
    }

    return { array, set: setArray, push, filter, update, remove, clear }
}

useArray 钩子利用 React 中的 useState 钩子来初始化和管理数组状态。它返回一个具有以下功能的对象:

  • push(element):将指定元素添加到数组中。
  • filter(callback):根据提供的回调函数过滤数组,删除不满足条件的元素。
  • update(index, newElement):用 newElement 替换指定索引处的元素。
  • remove(index):从数组中删除指定索引处的元素。
  • clear():清除数组,将其设置为空数组。

使用此自定义挂钩的优点是双重的:它简化了数组状态的管理并提供了更清晰、更易读的代码结构。使用 useArray 钩子,您可以轻松地添加、更新、删除、过滤和清除数组中的元素,而无需处理复杂的逻辑。

import useArray from "./useArray"

export default function ArrayComponent() {
    const { array, set, push, remove, filter, update, clear } = useArray([
        1, 2, 3, 4, 5, 6,
    ])

    return (
        <div>
            <div>{array.join(", ")}</div>
            <button onClick={() => push(7)}>Add 7</button>
            <button onClick={() => update(1, 9)}>Change Second Element To 9</button>
            <button onClick={() => remove(1)}>Remove Second Element</button>
            <button onClick={() => filter(n => n < 3)}>
                Keep Numbers Less Than 4
            </button>
            <button onClick={() => set([1, 2])}>Set To 1, 2</button>
            <button onClick={clear}>Clear</button>
        </div>
    )
}

2.使用异步

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useCallback, useEffect, useState } from "react"

export default function useAsync(callback, dependencies = []) {
    const [loading, setLoading] = useState(true)
    const [error, setError] = useState()
    const [value, setValue] = useState()

    const callbackMemoized = useCallback(() => {
        setLoading(true)
        setError(undefined)
        setValue(undefined)
        callback()
            .then(setValue)
            .catch(setError)
            .finally(() => setLoading(false))
    }, dependencies)

    useEffect(() => {
        callbackMemoized()
    }, [callbackMemoized])

    return { loading, error, value }
}

useAsync 挂钩接受执行异步操作的回调函数和可选的依赖项数组。它返回一个具有三个属性的对象:loading、error 和 value。load 属性指示操作当前是否正在进行,而 error 属性保存在此过程中遇到的任何错误消息。最后,value 属性包含异步操作的解析值。

useAsync 的显着优势之一是它能够使用 useCallback 记忆回调函数。这可确保仅在依赖项更改时重新创建回调,从而防止不必要的重新渲染并优化性能。此外,该钩子使用 useState 和 useEffect 钩子来管理加载状态并在必要时调用记忆的回调函数。

UseAsync 可用于多种场景。无论您是从 API 获取数据、执行计算还是处理表单提交,此自定义挂钩都可以简化整个 React 组件中异步操作的管理。它的灵活性和易用性使其成为任何 React 项目的宝贵补充。

通过利用 useAsync,您可以简化代码库、增强可重用性并保持一致且可靠的用户体验。在您的下一个 React 项目中尝试一下,见证简化异步操作的强大功能。

import useAsync from "./useAsync"

export default function AsyncComponent() {
    const { loading, error, value } = useAsync(() => {
        return new Promise((resolve, reject) => {
            const success = false
            setTimeout(() => {
                success ? resolve("Hi") : reject("Error")
            }, 1000)
        })
    })

    return (
        <div>
            <div>Loading: {loading.toString()}</div>
            <div>{error}</div>
            <div>{value}</div>
        </div>
    )
}

3.使用ClickOutside

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import useEventListener from "../useEventListener/useEventListener"

export default function useClickOutside(ref, cb) {
    useEventListener("click", e => {
        if (ref.current == null || ref.current.contains(e.target)) return
        cb(e)
    }, document)
}

useClickOutside 挂钩旨在简化检测指定组件外部点击的过程。通过利用 useEventListener 挂钩,它可以侦听文档级别的单击事件,从而允许您在所提供的组件引用之外发生单击时触发回调函数。

useClickOutside 的主要优点之一是它的易用性。只需将钩子导入您的组件并传递所需组件的引用和回调函数即可。该挂钩负责事件侦听器的设置和清理,从而节省您的时间和精力。另外,它还可以使用 useState 和 useRef 挂钩与功能组件无缝协作。

useClickOutside 的潜在应用是无穷无尽的。当实现模态窗口、下拉菜单或用户与外部任何内容交互时应关闭的任何元素时,它特别有用。通过合并 useClickOutside,您可以通过提供直观且高效的交互来增强用户体验。

import { useRef, useState } from "react"
import useClickOutside from "./useClickOutside"

export default function ClickOutsideComponent() {
    const [open, setOpen] = useState(false)
    const modalRef = useRef()

    useClickOutside(modalRef, () => {
        if (open) setOpen(false)
    })

    return (
        <>
            <button onClick={() => setOpen(true)}>Open</button>
            <div
                ref={modalRef}
                style={{
                    display: open ? "block" : "none",
                    backgroundColor: "blue",
                    color: "white",
                    width: "100px",
                    height: "100px",
                    position: "absolute",
                    top: "calc(50% - 50px)",
                    left: "calc(50% - 50px)",
                }}
            >
                <span>Modal</span>
            </div>
        </>
    )
}

要查看 useClickOutside 的实际效果,请查看上面的示例。在本例中,ClickOutsideComponent 使用挂钩来切换模式窗口的可见性。当用户在模态框外部单击时,提供的回调函数将打开状态设置为 false,从而关闭模态框。这样,该组件提供了一种时尚且用户友好的方式来管理模式的可见性。

4.使用Cookie

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useState, useCallback } from "react"
import Cookies from "js-cookie"

export default function useCookie(name, defaultValue) {
    const [value, setValue] = useState(() => {
        const cookie = Cookies.get(name)
        if (cookie) return cookie
        Cookies.set(name, defaultValue)
        return defaultValue
    })

    const updateCookie = useCallback(
        (newValue, options) => {
            Cookies.set(name, newValue, options)
            setValue(newValue)
        },
        [name]
    )

    const deleteCookie = useCallback(() => {
        Cookies.remove(name)
        setValue(null)
    }, [name])

    return [value, updateCookie, deleteCookie]
}

useCookie 钩子允许您通过提供简洁的接口轻松处理 cookie。初始化后,useCookie 检索具有指定名称的 cookie 值。如果cookie存在,则返回其值;否则,它将 cookie 设置为提供的默认值。这可以确保为您的用户提供无缝体验,因为所需的数据随时可用。

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

在需要删除 cookie 的情况下,deleteCookie 函数可以发挥作用。只需调用此函数,它就会从浏览器中删除指定的 cookie。该钩子负责更新状态,确保您的应用程序反映 cookie 的删除。

useCookie 自定义挂钩具有高度通用性,可以在各种上下文中使用。当处理用户首选项、身份验证令牌或需要在不同会话之间保留的任何数据时,它特别有用。无论您是构建简单的登录表单、购物车还是功能丰富的应用程序,useCookie 都可以简化 cookie 管理,为您节省宝贵的开发时间。

import useCookie from "./useCookie"

export default function CookieComponent() {
    const [value, update, remove] = useCookie("name", "John")

    return (
        <>
            <div>{value}</div>
            <button onClick={() => update("Sally")}>Change Name To Sally</button>
            <button onClick={remove}>Delete Name</button>
        </>
    )
}

5. 使用复制到剪贴板

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useState } from "react"
import copy from "copy-to-clipboard"

export default function useCopyToClipboard() {
    const [value, setValue] = useState()
    const [success, setSuccess] = useState()

    const copyToClipboard = (text, options) => {
        const result = copy(text, options)
        if (result) setValue(text)
        setSuccess(result)
    }

    return [copyToClipboard, { value, success }]
}

在 React 应用程序中将文本复制到剪贴板可能是一项繁琐的任务。为了简化此过程,我创建了一个名为 useCopyToClipboard 的强大自定义挂钩。只需几行代码,这个钩子就简化了复制到剪贴板的功能,为开发人员提供了一个无忧的解决方案。

useCopyToClipboard 钩子利用 React 中的 useState 钩子以及复制到剪贴板库来实现其功能。通过调用此自定义挂钩,您可以访问两个基本功能:copyToClipboard 及其附带的状态变量。

copyToClipboard 函数接受两个参数:要复制的文本和可选的配置选项。它处理复制过程并相应地更新状态。成功时,提供的文本将设置为当前值,并且成功状态设置为 true。相反,如果复制失败,则成功状态仍为 false。

为了演示 useCopyToClipboard 的强大功能,让我们考虑一个实际的实现。假设您有一个名为 CopyToClipboardComponent 的组件。通过利用此自定义挂钩,您可以通过调用 copyToClipboard 函数轻松复制文本,该函数接受所需的文本作为参数。成功状态变量提供即时反馈,允许您根据复制结果显示适当的消息或 UI 元素。

useCopyToClipboard 钩子的用途非常广泛,可以在各种场景中使用。它在需要复制文本(例如 URL、可共享内容或用户生成的数据)的情况下特别有用。无论您是构建博客平台、社交媒体应用程序还是任何其他基于 React 的项目,useCopyToClipboard 都可以简化复制文本的过程,从而增强用户体验和生产力。

import useCopyToClipboard from "./useCopyToClipboard"

export default function CopyToClipboardComponent() {
    const [copyToClipboard, { success }] = useCopyToClipboard()

    return (
        <>
            <button onClick={() => copyToClipboard("This was copied")}>
                {success ? "Copied" : "Copy Text"}
            </button>
            <input type="text" />
        </>
    )
}

6.使用深色模式

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useEffect } from "react"
import useMediaQuery from "../useMediaQuery/useMediaQuery"
import { useLocalStorage } from "../useStorage/useStorage"

export default function useDarkMode() {
    const [darkMode, setDarkMode] = useLocalStorage("useDarkMode")
    const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)")
    const enabled = darkMode ?? prefersDarkMode

    useEffect(() => {
        document.body.classList.toggle("dark-mode", enabled)
    }, [enabled])

    return [enabled, setDarkMode]
}

此自定义挂钩结合了其他两个方便的挂钩 useMediaQuery 和 useStorage,以提供无缝的暗模式体验。它会自动检测用户喜欢的配色方案,并将深色模式状态保留在浏览器的本地存储中。

“useDarkMode”的主要优点之一是它的简单性。只需几行代码,您就可以在 React 应用程序中启用暗模式。通过调用此挂钩,您将收到当前的暗模式状态和切换它的函数。

每当启用暗模式时,“useDarkMode”挂钩就会动态更新 HTML 正文类以应用“暗模式”样式。这种方法可确保所有组件之间的一致性,而无需手动进行类操作。

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

您可以在各种场景中使用“useDarkMode”挂钩。无论您是构建博客、电子商务平台还是内容较多的应用程序,深色模式都可以增强用户体验、减轻眼睛疲劳并节省设备电池寿命。可能性是无限的,这个自定义钩子使实施变得轻而易举。

为了使其更容易,我添加了一个简单的示例组件“DarkModeComponent”,它展示了如何使用“useDarkMode”挂钩。通过单击“切换深色模式”按钮,您可以立即在浅色和深色主题之间切换。按钮的外观会动态变化,反映当前模式。

import useDarkMode from "./useDarkMode"
import "./body.css"

export default function DarkModeComponent() {
    const [darkMode, setDarkMode] = useDarkMode()
    return (
        <button
            onClick={() => setDarkMode(prevDarkMode => !prevDarkMode)}
            style={{
                border: `1px solid ${darkMode ? "white" : "black"}`,
                background: "none",
                color: darkMode ? "white" : "black",
            }}
        >
            Toggle Dark Mode
        </button>
    )
}

7.使用Debounce

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useEffect } from "react"
import useTimeout from "../useTimeout/useTimeout"

export default function useDebounce(callback, delay, dependencies) {
    const { reset, clear } = useTimeout(callback, delay)
    useEffect(reset, [...dependencies, reset])
    useEffect(clear, [])
}

useDebounce 钩子在内部利用 useTimeout 钩子来延迟回调函数的执行,直到指定的延迟过去。通过这样做,它可以防止由于快速输入更改或重复事件而导致的频繁更新,从而实现更顺畅的交互并减少资源消耗。

useDebounce 的主要优点之一是它的简单性和灵活性。通过将回调函数、延迟持续时间和任何依赖项包装在此自定义挂钩中,您可以轻松实现去抖功能,而不会弄乱组件代码。该钩子负责管理超时并在必要时清除它,确保仅在指定的延迟后并使用最新的依赖项触发回调。

在哪里可以使用 useDebounce?可能性是无止境!此自定义挂钩在您需要处理用户输入(例如搜索栏或表单字段)的情况下特别有用,在这些情况下您希望延迟执行操作,直到用户完成键入或交互。它对于优化网络请求也很有用,确保仅在用户停止键入或选择选项后发送请求。

import { useState } from "react"
import useDebounce from "./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)}>Increment</button>
        </div>
    )
}

在上面的示例中,我们通过实现一个名为 DebounceComponent 的简单计数器组件来展示 useDebounce 的强大功能。每次用户单击“增量”按钮时,计数状态都会更新。然而,我们不是立即警告计数值,而是使用 useDebounce 来消除警报函数的抖动。计数值延迟1秒后才会报警,有效防止快速点击按钮时出现过多报警。

8.使用调试信息

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useEffect, useRef } from "react"
import useRenderCount from "../useRenderCount/useRenderCount"

export default function useDebugInformation(componentName, props) {
    const count = useRenderCount()
    const changedProps = useRef({})
    const previousProps = useRef(props)
    const lastRenderTimestamp = useRef(Date.now())
    const propKeys = Object.keys({ ...props, ...previousProps })
    changedProps.current = propKeys.reduce((obj, key) => {
        if (props[key] === previousProps.current[key]) return obj
        return {
            ...obj,
            [key]: { previous: previousProps.current[key], current: props[key] },
        }
    }, {})
    const info = {
        count,
        changedProps: changedProps.current,
        timeSinceLastRender: Date.now() - lastRenderTimestamp.current,
        lastRenderTimestamp: lastRenderTimestamp.current,
    }
    useEffect(() => {
        previousProps.current = props
        lastRenderTimestamp.current = Date.now()
        console.log("[debug-info]", componentName, info)
    })
    return info
}

当涉及到调试 React 组件时,访问有关渲染和 prop 更改的详细信息可能非常有用。这就是 useDebugInformation 自定义挂钩的用武之地。这个高级挂钩由 [Your Name] 创建,为开发人员提供了对其组件行为的宝贵见解,并帮助识别性能瓶颈或意外的渲染模式。

useDebugInformation 的主要优点之一是它的简单性。只需将几行代码集成到您的组件中,您就可以访问大量的调试数据。该钩子跟踪渲染次数、更改的道具、自上次渲染以来的时间以及上次渲染的时间戳。这些全面的信息使您能够更有效地分析组件行为,并在优化应用程序时做出明智的决策。

useDebugInformation 钩子可以应用于各种场景。例如,假设您正在开发一个复杂的表单组件,其中某些道具会触发更新或影响渲染。通过利用 useDebugInformation,您可以轻松监控这些 props 如何影响组件的性能以及是否发生不必要的重新渲染。此外,在调查特定组件未按预期更新的原因或在性能关键型应用程序中微调优化时,该钩子非常有用。

要实现 useDebugInformation,只需将其与任何其他必要的钩子一起导入到您的 React 组件中即可。在提供的示例中,DebugInformationComponent 使用 ChildComponent 中的 useDebugInformation 挂钩。通过将组件名称和属性传递给钩子,您可以访问包含所有相关调试数据的信息对象。然后可以显示或记录该对象以供进一步分析。

import useDebugInformation from "./useDebugInformation"
import useToggle from "../useToggle/useToggle"
import { useState } from "react"

export default function DebugInformationComponent() {
    const [boolean, toggle] = useToggle(false)
    const [count, setCount] = useState(0)
    return (
        <>
            <ChildComponent boolean={boolean} count={count} />
            <button onClick={toggle}>Toggle</button>
            <button onClick={() => setCount(prevCount => prevCount + 1)}>
                Increment
            </button>
        </>
    )
}
function ChildComponent(props) {
    const info = useDebugInformation("ChildComponent", props)
    return (
        <>
            <div>{props.boolean.toString()}</div>
            <div>{props.count}</div>
            <div>{JSON.stringify(info, null, 2)}</div>
        </>
    )
}

9.使用DeepCompareEffect

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useEffect, useRef } from "react"
import isEqual from "lodash/fp/isEqual"

export default function useDeepCompareEffect(callback, dependencies) {
    const currentDependenciesRef = useRef()
    if (!isEqual(currentDependenciesRef.current, dependencies)) {
        currentDependenciesRef.current = dependencies
    }
    useEffect(callback, [currentDependenciesRef.current])
}

在 React 中管理依赖关系可能是一个挑战,特别是在处理复杂的数据结构或嵌套对象时。这就是 useDeepCompareEffect 自定义挂钩派上用场的地方。useDeepCompareEffect 的创建是为了解决默认 useEffect 钩子的限制,它确保仅在依赖项发生深度更改时才触发效果回调,并使用 lodash 的 isEqual 函数进行准确比较。

useDeepCompareEffect 的主要优点之一是它能够防止不必要的重新渲染。通过对当前和之前的依赖关系进行深度比较,该钩子可以智能地确定是否应该触发该效果,从而在浅层比较无法满足的场景中优化性能。

在处理复杂的状态对象时,例如当您具有深度嵌套的数据结构或需要跟踪的多个互连状态时,此自定义挂钩特别有用。它使您能够定义准确反映您想要跟踪的特定更改的依赖项,确保仅在绝对必要时才执行效果。

您可以通过导入 useDeepCompareEffect 并使用它代替传统的 useEffect 钩子,轻松地将 useDeepCompareEffect 合并到您的 React 组件中。通过传递效果回调和一系列依赖项,您可以确保您的效果高效且有效地运行。

import { useEffect, useState, useRef } from "react"
import useDeepCompareEffect from "./useDeepCompareEffect"

export default function DeepCompareEffectComponent() {
    const [age, setAge] = useState(0)
    const [otherCount, setOtherCount] = useState(0)
    const useEffectCountRef = useRef()
    const useDeepCompareEffectCountRef = useRef()
    const person = { age: age, name: "Sergey" }
    useEffect(() => {
        useEffectCountRef.current.textContent =
            parseInt(useEffectCountRef.current.textContent) + 1
    }, [person])
    useDeepCompareEffect(() => {
        useDeepCompareEffectCountRef.current.textContent =
            parseInt(useDeepCompareEffectCountRef.current.textContent) + 1
    }, [person])
    return (
        <div>
            <div>
                useEffect: <span ref={useEffectCountRef}>0</span>
            </div>
            <div>
                useDeepCompareEffect: <span ref={useDeepCompareEffectCountRef}>0</span>
            </div>
            <div>Other Count: {otherCount}</div>
            <div>{JSON.stringify(person)}</div>
            <button onClick={() => setAge(currentAge => currentAge + 1)}>
                Increment Age
            </button>
            <button onClick={() => setOtherCount(count => count + 1)}>
                Increment Other Count
            </button>
        </div>
    )
}

10. 使用效果一次

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useEffect } from "react"

export default function useEffectOnce(cb) {
    useEffect(cb, [])
}

useEffectOnce 挂钩旨在简化组件安装时仅运行一次效果的过程。只需几行代码,您就无需手动指定空依赖项数组 ([])。它的工作原理如下:

通过封装重复的 useEffect 模式,useEffectOnce 允许您专注于效果函数本身的逻辑。这个优雅的解决方案使您无需重复编写样板代码,并有助于保持组件文件干净简洁。

为了展示 useEffectOnce 的强大功能,让我们考虑一个实际示例:

import { useState } from "react"
import useEffectOnce from "./useEffectOnce"

export default function EffectOnceComponent() {
    const [count, setCount] = useState(0)
    useEffectOnce(() => alert("Hi"))
    return (
        <>
            <div>{count}</div>
            <button onClick={() => setCount(c => c + 1)}>Increment</button>
        </>
    )
}

在这种情况下,当安装 EffectOnceComponent 时,useEffectOnce 挂钩只会触发一次警报“Hi”。它使您无需手动管理效果依赖项,并确保您的效果高效运行。

这个定制挂钩非常通用,可以在各种场景中使用。无论您需要获取初始数据、设置事件侦听器还是初始化第三方库,useEffectOnce 都可以简化流程并促进更清晰的代码组织。

11. 使用事件监听器

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useEffect, useRef } from "react"

export default function useEventListener(
    eventType,
    callback,
    element = window
) {
    const callbackRef = useRef(callback)
    useEffect(() => {
        callbackRef.current = callback
    }, [callback])
    useEffect(() => {
        if (element == null) return
        const handler = e => callbackRef.current(e)
        element.addEventListener(eventType, handler)
        return () => element.removeEventListener(eventType, handler)
    }, [eventType, element])
}

useEventListener 的主要优点之一是它的灵活性。您可以指定事件类型、回调函数,甚至事件侦听器应附加的元素。这种灵活性使您可以根据您的特定需求定制事件处理,从而增强代码的可重用性。

该钩子还利用 useRef 钩子来维护对回调函数的稳定引用。这可以确保使用最新版本的回调,即使它在组件的生命周期中发生变化。这种动态行为使您能够精确处理事件并响应应用程序状态的变化。

useEventListener 钩子是一个多功能工具,可用于多种场景。无论您需要捕获键盘事件、监听滚动事件还是与用户输入交互,这个钩子都能满足您的需求。它的简单性和优雅性使其成为任何 React 项目的理想选择,从小型应用程序到大型企业解决方案。

为了演示 useEventListener 的强大功能,请考虑提供的 EventListenerComponent。它利用钩子来跟踪用户最后按下的键。只需几行代码,您就可以轻松处理 keydown 事件并相应地更新组件的状态。此示例强调了 useEventListener 的易用性和有效性,展示了它简化 React 应用程序中事件驱动交互的能力。

import { useState } from "react"
import useEventListener from "./useEventListener"

export default function EventListenerComponent() {
    const [key, setKey] = useState("")
    useEventListener("keydown", e => {
        setKey(e.key)
    })
    return <div>Last Key: {key}</div>
}

12.使用Fetch

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import useAsync from "../useAsync/useAsync"

const DEFAULT_OPTIONS = {
    headers: { "Content-Type": "application/json" },
}
export default function useFetch(url, options = {}, dependencies = []) {
    return useAsync(() => {
        return fetch(url, { ...DEFAULT_OPTIONS, ...options }).then(res => {
            if (res.ok) return res.json()
            return res.json().then(json => Promise.reject(json))
        })
    }, dependencies)
}

useFetch 的主要优点之一是它的简单性。通过将获取逻辑抽象为可重用的钩子,开发人员可以快速、轻松地发出 HTTP 请求并处理响应,而无需重复的样板代码。useFetch 只需几行即可处理网络请求、解析 JSON 响应并提供结果数据。

useFetch 钩子还通过其可定制的选项参数提供了灵活性。开发人员可以根据需要传递额外的标头、查询参数或请求选项,确保与各种 API 的兼容性。该钩子遵循最佳实践,提供将 Content-Type 标头设置为 application/json 的默认选项,从而促进干净且一致的代码。

useFetch 的另一个值得注意的功能是它对依赖项跟踪的支持。通过指定依赖项数组,开发人员可以控制钩子何时触发新请求。此功能增强了性能优化,允许根据依赖项数组的变化进行选择性数据更新。

这种多功能挂钩可用于多种场景。例如,在需要获取和显示动态数据的 React 组件中,useFetch 简化了该过程。它负责处理加载和错误状态,保持组件干净并专注于渲染接收到的数据。此外,useFetch 在获取的数据基于动态变量或用户交互的场景中特别有用,如 FetchComponent 示例中所示。

import { useState } from "react"
import useFetch from "./useFetch"

export default function FetchComponent() {
    const [id, setId] = useState(1)
    const { loading, error, value } = useFetch(
        `https://jsonplaceholder.typicode.com/todos/${id}`,
        {},
        [id]
    )
    return (
        <div>
            <div>{id}</div>
            <button onClick={() => setId(currentId => currentId + 1)}>
                Increment ID
            </button>
            <div>Loading: {loading.toString()}</div>
            <div>{JSON.stringify(error, null, 2)}</div>
            <div>{JSON.stringify(value, null, 2)}</div>
        </div>
    )
}

13.使用地理位置

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useState, useEffect } from "react"

export default function useGeolocation(options) {
    const [loading, setLoading] = useState(true)
    const [error, setError] = useState()
    const [data, setData] = useState({})
    useEffect(() => {
        const successHandler = e => {
            setLoading(false)
            setError(null)
            setData(e.coords)
        }
        const errorHandler = e => {
            setError(e)
            setLoading(false)
        }
        navigator.geolocation.getCurrentPosition(
            successHandler,
            errorHandler,
            options
        )
        const id = navigator.geolocation.watchPosition(
            successHandler,
            errorHandler,
            options
        )
        return () => navigator.geolocation.clearWatch(id)
    }, [options])
    return { loading, error, data }
}

useGeolocation 钩子利用 React 的 useState 和 useEffect 钩子来管理加载状态、错误和地理位置数据。它需要一个可选的“选项”参数来自定义地理定位行为,允许您根据您的特定需求微调准确性和其他设置。

useGeolocation 的主要优点之一是它的简单性。通过封装地理位置访问和处理所需的复杂逻辑,该挂钩提供了一个干净且可重用的解决方案。该钩子自动处理加载状态,在获取地理位置数据时更新它,并在过程中出现任何问题时设置错误状态。

useGeolocation 挂钩还合并了 Geolocation API 中的 watchPosition 方法,该方法可以持续监控用户的位置。这在需要实时更新用户位置的场景中非常有用,例如在跟踪应用程序或交互式地图中。

要使用此钩子,只需将 useGeolocation 导入到您的组件中并解构加载、错误和数据变量。该数据对象包含纬度和经度值,使您可以轻松地在 UI 上显示用户的位置。加载变量通知您地理位置检索的当前状态,错误变量提供任何错误消息(如果适用)。

import useGeolocation from "./useGeolocation"

export default function GeolocationComponent() {
    const {
        loading,
        error,
        data: { latitude, longitude },
    } = useGeolocation()
    return (
        <>
            <div>Loading: {loading.toString()}</div>
            <div>Error: {error?.message}</div>
            <div>
                {latitude} x {longitude}
            </div>
        </>
    )
}

上面展示的 GeolocationComponent 演示了 useGeolocation 的基本实现。它呈现加载状态、错误消息(如果有)以及用户的纬度和经度值。只需几行代码,您就可以将地理定位功能无缝集成到您的 React 应用程序中。

14.使用悬停

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useState } from "react"
import useEventListener from "../useEventListener/useEventListener"

export default function useHover(ref) {
    const [hovered, setHovered] = useState(false)
    useEventListener("mouseover", () => setHovered(true), ref.current)
    useEventListener("mouseout", () => setHovered(false), ref.current)
    return hovered
}

这个轻量级钩子利用 React 中的 useState 和 useEventListener 钩子来跟踪悬停状态。只需将引用传递给 useHover 挂钩,您就可以开始接收准确的悬停事件。该钩子监听“mouseover”和“mouseout”事件,相应地更新悬停状态。

useHover 的主要优点之一是它的简单性和可重用性。通过将悬停逻辑封装在钩子中,您可以轻松地跨多个组件使用它,而无需重复代码。这可以促进代码的干净和可维护,从长远来看可以节省您的时间和精力。

UseHover 可用于多种场景。无论您需要在悬停时突出显示某个元素、触发其他操作还是动态更改样式,这个自定义挂钩都可以满足您的需求。它提供了一种无缝的方式来增强 React 组件的交互性和用户体验。

import { useRef } from "react"
import useHover from "./useHover"

export default function HoverComponent() {
    const elementRef = useRef()
    const hovered = useHover(elementRef)
    return (
        <div
            ref={elementRef}
            style={{
                backgroundColor: hovered ? "blue" : "red",
                width: "100px",
                height: "100px",
                position: "absolute",
                top: "calc(50% - 50px)",
                left: "calc(50% - 50px)",
            }}
        />
    )
}

为了展示它的强大功能,请考虑上面的 HoverComponent 示例。通过将 useHover 挂钩应用于 elementRef,div 的背景颜色根据悬停状态在蓝色和红色之间动态变化。这个简单而有效的实现展示了 useHover 在创建交互式和引人入胜的 UI 组件方面的潜力。

15.使用长按

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import useEventListener from "../useEventListener/useEventListener"
import useTimeout from "../useTimeout/useTimeout"
import useEffectOnce from "../useEffectOnce/useEffectOnce"

export default function useLongPress(ref, cb, { delay = 250 } = {}) {
    const { reset, clear } = useTimeout(cb, delay)
    useEffectOnce(clear)
    useEventListener("mousedown", reset, ref.current)
    useEventListener("touchstart", reset, ref.current)
    useEventListener("mouseup", clear, ref.current)
    useEventListener("mouseleave", clear, ref.current)
    useEventListener("touchend", clear, ref.current)
}

useLongPress 的主要优点之一是它的简单性。通过利用这个钩子,开发人员可以轻松地在 React 应用程序中的任何元素上定义长按操作。只需几行代码,该钩子就可以处理跟踪长按持续时间并触发相关回调函数的复杂问题。

useLongPress 挂钩通过可定制的选项提供灵活性。开发人员可以指定长按所需的延迟,从而允许他们微调触发操作所需的持续时间。此外,该钩子还可以与 useTimeout、useEventListener 和 useEffectOnce 等其他自定义钩子智能集成,从而增强代码的可重用性和可维护性。

LongPress 的应用范围非常广泛。无论您是开发触摸敏感 UI、实现上下文菜单还是创建自定义手势,这个挂钩都被证明是一个有价值的工具。从移动应用程序到复杂的 Web 界面,useLongPress 提供了一个优雅的解决方案,用于合并长按交互,从而提高用户参与度并提高整体可用性。

import { useRef } from "react"
import useLongPress from "./useLongPress"

export default function LongPressComponent() {
    const elementRef = useRef()
    useLongPress(elementRef, () => alert("Long Press"))
    return (
        <div
            ref={elementRef}
            style={{
                backgroundColor: "red",
                width: "100px",
                height: "100px",
                position: "absolute",
                top: "calc(50% - 50px)",
                left: "calc(50% - 50px)",
            }}
        />
    )
}

16. 使用MediaQuery

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useState, useEffect } from "react"
import useEventListener from "../useEventListener/useEventListener"

export default function useMediaQuery(mediaQuery) {
    const [isMatch, setIsMatch] = useState(false)
    const [mediaQueryList, setMediaQueryList] = useState(null)
    useEffect(() => {
        const list = window.matchMedia(mediaQuery)
        setMediaQueryList(list)
        setIsMatch(list.matches)
    }, [mediaQuery])
    useEventListener("change", e => setIsMatch(e.matches), mediaQueryList)
    return isMatch
}

useMediaQuery 挂钩允许您根据给定的媒体查询动态更新 UI。只需传入所需的媒体查询作为参数,挂钩将返回一个布尔值,指示媒体查询是否与当前视口大小匹配。

此自定义挂钩的主要优点之一是其简单性和可重用性。只需几行代码,您就可以轻松地在整个应用程序中实现响应式行为。无论您需要有条件地渲染组件、应用特定样式还是根据屏幕尺寸触发不同的功能,useMediaQuery 都能满足您的需求。

这个钩子不限于特定的用例;它可以在多种场景中使用。例如,您可以使用它动态调整导航菜单的布局,根据屏幕尺寸隐藏或显示某些元素,甚至根据可用空间优化数据加载。可能性是无限的,useMediaQuery 挂钩使您能够跨不同设备和屏幕尺寸提供无缝的用户体验。

import useMediaQuery from "./useMediaQuery"

export default function MediaQueryComponent() {
    const isLarge = useMediaQuery("(min-width: 200px)")
    return <div>Large: {isLarge.toString()}</div>
}

17. 使用在线状态

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useState } from "react"
import useEventListener from "../useEventListener/useEventListener"

export default function useOnlineStatus() {
    const [online, setOnline] = useState(navigator.onLine)
    useEventListener("online", () => setOnline(navigator.onLine))
    useEventListener("offline", () => setOnline(navigator.onLine))
    return online
}

“useOnlineStatus”的主要优点之一是它的简单性。通过在组件中导入并使用此钩子,您可以轻松访问用户的在线状态。该钩子在内部使用“navigator.onLine”属性来确定初始在线状态,并在用户连接发生变化时动态更新它。

要使用此钩子,您所需要做的就是在功能组件中调用它,就像“OnlineStatusComponent”示例演示的那样。它返回一个布尔值,指示用户当前是在线还是离线。然后,您可以利用此信息向用户提供实时反馈或根据他们的在线状态做出决策。

“useOnlineStatus”挂钩可以在多种场景中找到应用程序。例如,您可以通过在用户失去互联网连接时显示视觉指示器来增强用户体验,从而允许他们采取适当的操作。此外,您可以根据用户的在线状态有条件地渲染某些组件或触发特定行为。可能性是无限的,这个钩子为构建健壮且响应灵敏的 React 应用程序开辟了新的机会。

import useOnlineStatus from "./useOnlineStatus"

export default function OnlineStatusComponent() {
    const online = useOnlineStatus()
    return <div>{online.toString()}</div>
}

18.使用屏幕

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useEffect, useState } from "react"

export default function useOnScreen(ref, rootMargin = "0px") {
    const [isVisible, setIsVisible] = useState(false)
    useEffect(() => {
        if (ref.current == null) return
        const observer = new IntersectionObserver(
            ([entry]) => setIsVisible(entry.isIntersecting),
            { rootMargin }
        )
        observer.observe(ref.current)
        return () => {
            if (ref.current == null) return
            observer.unobserve(ref.current)
        }
    }, [ref.current, rootMargin])
    return isVisible
}

useOnScreen 挂钩利用 Intersection Observer API 的强大功能,使其高效可靠。只需提供对要监视的元素的引用,useOnScreen 就会在其进入或退出视口时通知您。

useOnScreen 的主要优点之一是它的简单性。只需几行代码,您就可以检测元素是否可见并做出相应的响应。这在您想要触发动画、延迟加载图像或在用户滚动时加载其他内容的情况下非常有用。

要使用此钩子,首先将其导入到组件文件中。然后,使用 useRef 挂钩创建一个 ref 以定位所需的元素。将 ref 作为第一个参数传递给 useOnScreen 挂钩,然后一切就完成了!您还可以提供可选的 rootMargin 值来调整可见阈值。

在我们的示例代码中,OnScreenComponentComponent 演示了如何使用 useOnScreen 挂钩。通过将 ref 附加到第二个标题元素,我们可以在它进入视口时显示“(Visible)”文本。您可以随意自定义组件内的逻辑以满足您的特定需求。

import { useRef } from "react"
import useOnScreen from "./useOnScreen"

export default function OnScreenComponentComponent() {
    const headerTwoRef = useRef()
    const visible = useOnScreen(headerTwoRef, "-100px")
    return (
        <div>
            <h1>Header</h1>
            <div>
              ...
            </div>
            <h1 ref={headerTwoRef}>Header 2 {visible && "(Visible)"}</h1>
            <div>
              ...
            </div>
        </div>
    )
}

19. 使用上一个

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useRef } from "react"

export default function usePrevious(value) {
    const currentRef = useRef(value)
    const previousRef = useRef()
    if (currentRef.current !== value) {
        previousRef.current = currentRef.current
        currentRef.current = value
    }
    return previousRef.current
}

使用 usePrevious 的优点是显着的。通过使用 useRef,这个钩子可以有效地存储当前和以前的值,并在值发生变化时更新它们。通过比较当前值和以前的值,您可以轻松检测组件数据的变化并做出响应。

这个自定义钩子可以在各种场景中改变游戏规则。例如,您可以利用 usePrevious 来比较和可视化数据变化、跟踪状态转换或实现撤消/重做功能。此外,它在表单处理、动画以及任何访问先前值对于应用程序逻辑至关重要的情况下都很有价值。

让我们看一下 usePrevious 在实践中是如何使用的。考虑一个名为 PreviousComponent 的 React 组件,其中我们有一个计数状态、一个名称状态和一个用于增加计数和更改名称的按钮。通过合并 usePrevious,我们可以轻松地显示当前计数及其之前的值,使用户能够一目了然地看到计数的变化。

import { useState } from "react"
import usePrevious from "./usePrevious"

export default function PreviousComponent() {
    const [count, setCount] = useState(0)
    const [name, setName] = useState("Sergey")
    const previousCount = usePrevious(count)
    return (
        <div>
            <div>
                {count} - {previousCount}
            </div>
            <div>{name}</div>
            <button onClick={() => setCount(currentCount => currentCount + 1)}>
                Increment
            </button>
            <button onClick={() => setName("John")}>Change Name</button>
        </div>
    )
}

20. 使用渲染计数

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useEffect, useRef } from "react"

export default function useRenderCount() {
    const count = useRef(1)
    useEffect(() => count.current++)
    return count.current
}

useRenderCount 钩子利用 React 的 useEffect 和 useRef 钩子来保存渲染计数。每次渲染时,计数都会增加,为您提供有关组件渲染频率的实时反馈。

使用 useRenderCount 的主要优点之一是它的简单性。通过将逻辑抽象为可重用的钩子,您可以轻松地将其集成到任何组件中,而不会弄乱您的代码库。此外,它还提供了一种清晰简洁的方式来监控渲染行为,这对于性能优化和调试至关重要。

这种多功能的钩子可以应用于各种场景。例如,当您开发一个呈现意外渲染模式的复杂组件时,useRenderCount 通过显示确切的渲染次数来帮助您查明问题。它还可以方便地测量某些优化或重构技术的影响,使您能够评估其有效性。

import useRenderCount from "./useRenderCount"
import useToggle from "../useToggle/useToggle"

export default function RenderCountComponent() {
    const [boolean, toggle] = useToggle(false)
    const renderCount = useRenderCount()
    return (
        <>
            <div>{boolean.toString()}</div>
            <div>{renderCount}</div>
            <button onClick={toggle}>Toggle</button>
        </>
    )
}

首先,只需导入 useRenderCount 挂钩并在组件中调用它即可。通过查看上面的 RenderCountComponent 示例,您可以看到它的强大功能。通过将 useRenderCount 与 useToggle 等其他自定义挂钩相结合,您可以构建交互式组件,同时关注渲染计数。

21. 使用脚本

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import useAsync from "../useAsync/useAsync"

export default function useScript(url) {
    return useAsync(() => {
        const script = document.createElement("script")
        script.src = url
        script.async = true
        return new Promise((resolve, reject) => {
            script.addEventListener("load", resolve)
            script.addEventListener("error", reject)
            document.body.appendChild(script)
        })
    }, [url])
}

useScript 的显着优势之一是它能够异步处理脚本加载。通过将脚本的 async 属性设置为 true,您可以确保它不会阻止应用程序的呈现。这可以提高性能和整体用户体验,特别是在处理较大的脚本或缓慢的网络连接时。

UseScript可以用于各种场景。例如,您可以加载 jQuery 等外部库,从而使您能够利用其强大的功能,而无需向包中添加大量内容。此外,您还可以加载分析脚本、社交媒体小部件或应用程序动态行为所需的任何其他脚本。

import useScript from "./useScript"

export default function ScriptComponent() {
    const { loading, error } = useScript(
        "https://code.jquery.com/jquery-3.6.0.min.js"
    )
    if (loading) return <div>Loading</div>
    if (error) return <div>Error</div>
    return <div>{window.$(window).width()}</div>
}

在上面的示例中,我们看到了如何在 ScriptComponent 中使用 useScript。使用 jQuery 库的 URL 作为参数来调用 useScript 挂钩。该钩子返回加载和错误状态,可用于相应地显示加载旋转器或错误消息。成功加载脚本后,该组件将使用 jQuery 显示当前窗口宽度。

22. 使用状态与历史记录

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useCallback, useRef, useState } from "react"

export default function useStateWithHistory(
    defaultValue,
    { capacity = 10 } = {}
) {
    const [value, setValue] = useState(defaultValue)
    const historyRef = useRef([value])
    const pointerRef = useRef(0)
    const set = useCallback(
        v => {
            const resolvedValue = typeof v === "function" ? v(value) : v
            if (historyRef.current[pointerRef.current] !== resolvedValue) {
                if (pointerRef.current < historyRef.current.length - 1) {
                    historyRef.current.splice(pointerRef.current + 1)
                }
                historyRef.current.push(resolvedValue)
                while (historyRef.current.length > capacity) {
                    historyRef.current.shift()
                }
                pointerRef.current = historyRef.current.length - 1
            }
            setValue(resolvedValue)
        },
        [capacity, value]
    )
    const back = useCallback(() => {
        if (pointerRef.current <= 0) return
        pointerRef.current--
        setValue(historyRef.current[pointerRef.current])
    }, [])
    const forward = useCallback(() => {
        if (pointerRef.current >= historyRef.current.length - 1) return
        pointerRef.current++
        setValue(historyRef.current[pointerRef.current])
    }, [])
    const go = useCallback(index => {
        if (index < 0 || index > historyRef.current.length - 1) return
        pointerRef.current = index
        setValue(historyRef.current[pointerRef.current])
    }, [])
    return [        value,        set,        {            history: historyRef.current,            pointer: pointerRef.current,            back,            forward,            go,        },    ]
}

useStateWithHistory 的优点:

  1. 自动历史记录跟踪:useStateWithHistory 自动跟踪您设置的值,使您可以在需要时访问完整的历史记录。
  2. 高效的内存使用:该钩子使用容量参数,确保历史记录不会无限期增长。您可以定义要保留的历史值的最大数量,以防止过多的内存消耗。
  3. 时间旅行功能:通过 back()、forward() 和 go() 函数,您可以无缝地浏览记录的历史记录。在之前的状态之间来回移动或直接跳转到特定索引,从而实现强大的撤消/重做或逐步功能。

在哪里使用useStateWithHistory:

  1. 表单管理:通过提供一种简单的方法来跟踪更改、恢复到以前的值或重做修改,从而简化处理表单输入的过程。
  2. 撤消/重做功能:轻松在应用程序中实现撤消/重做功能。跟踪状态变化并允许用户轻松地来回导航他们的操作。
  3. 分步导航:使用 useStateWithHistory 构建交互式指南或教程,用户可以在不同步骤之间导航,同时保留进度。
import { useState } from "react"
import useStateWithHistory from "./useStateWithHistory"

export default function StateWithHistoryComponent() {
    const [count, setCount, { history, pointer, back, forward, go }] =
        useStateWithHistory(1)
    const [name, setName] = useState("Sergey")
    return (
        <div>
            <div>{count}</div>
            <div>{history.join(", ")}</div>
            <div>Pointer - {pointer}</div>
            <div>{name}</div>
            <button onClick={() => setCount(currentCount => currentCount * 2)}>
                Double
            </button>
            <button onClick={() => setCount(currentCount => currentCount + 1)}>
                Increment
            </button>
            <button onClick={back}>Back</button>
            <button onClick={forward}>Forward</button>
            <button onClick={() => go(2)}>Go To Index 2</button>
            <button onClick={() => setName("John")}>Change Name</button>
        </div>
    )
}

23. 使用状态验证

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useState, useCallback } from "react"

export default function useStateWithValidation(validationFunc, initialValue) {
    const [state, setState] = useState(initialValue)
    const [isValid, setIsValid] = useState(() => validationFunc(state))
    const onChange = useCallback(
        nextState => {
            const value =
                typeof nextState === "function" ? nextState(state) : nextState
            setState(value)
            setIsValid(validationFunc(value))
        },
        [validationFunc]
    )
    return [state, onChange, isValid]
}

useStateWithValidation 挂钩结合了 React 中的 useState 和 useCallback 挂钩,提供了一个优雅的解决方案。它需要两个参数:验证函数和初始值。验证函数确定当前状态是否有效。

这种定制挂钩的主要优点之一是其灵活性。您可以通过任何适合您特定要求的验证函数。无论是检查字符串的长度、确保数值在特定范围内,还是执行更复杂的验证,useStateWithValidation 都能满足您的需求。

import useStateWithValidation from "./useStateWithValidation"

export default function StateWithValidationComponent() {
    const [username, setUsername, isValid] = useStateWithValidation(
        name => name.length > 5,
        ""
    )
    return (
        <>
            <div>Valid: {isValid.toString()}</div>
            <input
                type="text"
                value={username}
                onChange={e => setUsername(e.target.value)}
            />
        </>
    )
}

在此示例中,StateWithValidationComponent 使用 useStateWithValidation 挂钩来管理用户名状态。验证函数检查用户名的长度是否大于5个字符,isValid变量反映当前输入的有效性。

24. 使用存储

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useCallback, useState, useEffect } from "react"

export function useLocalStorage(key, defaultValue) {
    return useStorage(key, defaultValue, window.localStorage)
}
export function useSessionStorage(key, defaultValue) {
    return useStorage(key, defaultValue, window.sessionStorage)
}
function useStorage(key, defaultValue, storageObject) {
    const [value, setValue] = useState(() => {
        const jsonValue = storageObject.getItem(key)
        if (jsonValue != null) return JSON.parse(jsonValue)
        if (typeof defaultValue === "function") {
            return defaultValue()
        } else {
            return defaultValue
        }
    })
    useEffect(() => {
        if (value === undefined) return storageObject.removeItem(key)
        storageObject.setItem(key, JSON.stringify(value))
    }, [key, value, storageObject])
    const remove = useCallback(() => {
        setValue(undefined)
    }, [])
    return [value, setValue, remove]
}

useStorage 钩子提供了两个方便的函数:useLocalStorage 和 useSessionStorage。使用 useLocalStorage,您可以轻松地在浏览器的本地存储中存储和检索数据,而 useSessionStorage 提供相同的功能,但使用会话存储。

此自定义挂钩的主要优点之一是其简单性。您可以使用它来存储任何类型的数据,例如字符串、数字,甚至复杂的对象,只需几行代码。此外,useStorage 会为您处理数据的序列化和反序列化,因此您不必担心值与 JSON 之间的转换。

另一个优点是存储的数据和组件状态之间的自动同步。每当存储的数据发生变化时,挂钩就会相应地更新组件的状态。类似地,当组件的状态发生变化时,钩子会自动将新值保存到存储中。这种双向同步可确保您的应用程序始终反映最新数据,非常适合实时更新至关重要的场景。

useStorage 钩子还提供了删除功能,使您可以在不再需要时轻松删除存储的值。在实现注销按钮或清除用户特定数据等功能时,此功能会派上用场。

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

import { useSessionStorage, useLocalStorage } from "./useStorage"

export default function StorageComponent() {
    const [name, setName, removeName] = useSessionStorage("name", "Sergey")
    const [age, setAge, removeAge] = useLocalStorage("age", 26)
    return (
        <div>
            <div>
                {name} - {age}
            </div>
            <button onClick={() => setName("John")}>Set Name</button>
            <button onClick={() => setAge(40)}>Set Age</button>
            <button onClick={removeName}>Remove Name</button>
            <button onClick={removeAge}>Remove Age</button>
        </div>
    )
}

25. 使用超时

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useCallback, useEffect, useRef } from "react"

export default function useTimeout(callback, delay) {
    const callbackRef = useRef(callback)
    const timeoutRef = useRef()
    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来记忆“set”和“clear”函数来优化性能。这意味着只有当函数的依赖关系发生变化时才会重新创建函数,从而防止不必要的渲染并提高效率。

“useTimeout”挂钩可用于需要定时操作的各种场景。例如,在上面展示的“TimeoutComponent”这样的倒计时组件中,您可以轻松实现一个在特定持续时间后重置的计时器。通过使用“useTimeout”挂钩,您可以轻松更新倒计时值并管理超时,而无需担心复杂的超时管理代码。

import { useState } from "react"
import useTimeout from "./useTimeout"

export default function TimeoutComponent() {
    const [count, setCount] = useState(10)
    const { clear, reset } = useTimeout(() => setCount(0), 1000)
    return (
        <div>
            <div>{count}</div>
            <button onClick={() => setCount(c => c + 1)}>Increment</button>
            <button onClick={clear}>Clear Timeout</button>
            <button onClick={reset}>Reset Timeout</button>
        </div>
    )
}

26. 使用切换

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useState } from "react"

export default function useToggle(defaultValue) {
    const [value, setValue] = useState(defaultValue)
    function toggleValue(value) {
        setValue(currentValue =>
            typeof value === "boolean" ? value : !currentValue
        )
    }
    return [value, toggleValue]
}

useToggle 的主要优点之一是它的灵活性。使用一行代码,您可以使用默认值初始化状态。toggleValue 函数允许您轻松地在 true 和 false 之间切换状态,或者您可以直接传递布尔值以将状态设置为您想要的值。这种多功能性使 useToggle 非常适合需要切换或切换状态的各种场景。

UseToggle 可以无缝集成到各种 React 组件中。例如,在提供的 ToggleComponent 中,useToggle 挂钩用于管理切换按钮的状态。只需单击一下,按钮的状态就会在 true 和 false 之间切换。此外,该钩子还提供按钮来直接将值设置为 true 或 false,以满足特定的用例。结果状态会动态显示,从而实现即时反馈。

import useToggle from "./useToggle"

export default function ToggleComponent() {
    const [value, toggleValue] = useToggle(false)
    return (
        <div>
            <div>{value.toString()}</div>
            <button onClick={toggleValue}>Toggle</button>
            <button onClick={() => toggleValue(true)}>Make True</button>
            <button onClick={() => toggleValue(false)}>Make False</button>
        </div>
    )
}

27. 使用翻译

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useLocalStorage } from "../useStorage/useStorage"
import * as translations from "./translations"

export default function useTranslation() {
    const [language, setLanguage] = useLocalStorage("language", "en")
    const [fallbackLanguage, setFallbackLanguage] = useLocalStorage(
        "fallbackLanguage",
        "en"
    )
    const translate = key => {
        const keys = key.split(".")
        return (
            getNestedTranslation(language, keys) ??
            getNestedTranslation(fallbackLanguage, keys) ??
            key
        )
    }
    return {
        language,
        setLanguage,
        fallbackLanguage,
        setFallbackLanguage,
        t: translate,
    }
}
function getNestedTranslation(language, keys) {
    return keys.reduce((obj, key) => {
        return obj?.[key]
    }, translations[language])
}

useTranslation 的主要优势之一是它与浏览器的 localStorage 的无缝集成。它会自动保存所选的语言和后备语言首选项,因此您的用户每次访问您的应用程序时都会看到其首选语言的内容。

该挂钩利用 useStorage 库中的 useLocalStorage 挂钩来保存语言设置。这确保即使用户刷新页面或导航离开并返回,他们的语言首选项也将被保留。

使用 useTranslation 非常简单。只需导入钩子并在组件中初始化它即可。您将可以访问当前语言、设置语言的能力、后备语言以及设置后备语言的选项。此外,该钩子还提供了一个方便的转换函数 t,它以键作为输入并返回相应的转换值。

您可以在各种场景中使用 useTranslation 挂钩。无论您是构建多语言网站、国际化应用程序,还是仅仅需要在 UI 组件中支持翻译,此挂钩都将简化流程并使您的代码库更易于维护。

import useTranslation from "./useTranslation"

export default function TranslationComponent() {
    const { language, setLanguage, setFallbackLanguage, t } = useTranslation()
    return (
        <>
            <div>{language}</div>
            <div>{t("hi")}</div>
            <div>{t("bye")}</div>
            <div>{t("nested.value")}</div>
            <button onClick={() => setLanguage("sp")}>Change To Spanish</button>
            <button onClick={() => setLanguage("en")}>Change To English</button>
            <button onClick={() => setFallbackLanguage("sp")}>Change FB Lang</button>
        </>
    )
}

28. 使用更新效果

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useEffect, useRef } from "react"

export default function useUpdateEffect(callback, dependencies) {
    const firstRenderRef = useRef(true)
    useEffect(() => {
        if (firstRenderRef.current) {
            firstRenderRef.current = false
            return
        }
        return callback()
    }, dependencies)
}

useUpdateEffect 挂钩设计为仅在初始渲染后执行回调函数。当您想要根据状态更改执行操作同时跳过初始执行时,此行为特别有用。通过利用 useRef 挂钩,useUpdateEffect 跟踪第一次渲染并跳过该阶段的回调。

useUpdateEffect 的主要优点之一是它的简单性。只需几行代码,您就可以通过有效处理状态更新来增强您的 React 组件。通过指定钩子的依赖关系,您可以精确控制何时触发回调,从而防止不必要的渲染周期。

这个自定义钩子可以在各种场景中使用。例如,假设您有一个计数器组件,每次计数发生变化时(不包括初始渲染)都需要显示警报。通过使用 useUpdateEffect,您可以轻松实现此行为,从而改善用户体验并减少不必要的警报。

要实现 useUpdateEffect,只需将其导入到 React 组件中并定义回调函数和依赖项即可。钩子将处理其余的事情,确保仅在必要时执行回调。它是一个强大的工具,可以简化状态管理并增强 React 应用程序的性能。

import { useState } from "react"
import useUpdateEffect from "./useUpdateEffect"

export default function UpdateEffectComponent() {
    const [count, setCount] = useState(10)
    useUpdateEffect(() => alert(count), [count])
    return (
        <div>
            <div>{count}</div>
            <button onClick={() => setCount(c => c + 1)}>Increment</button>
        </div>
    )
}

29. 使用窗口大小

来源:  https: //github.com/sergeyleschev/react-custom-hooks

import { useState } from "react"
import useEventListener from "../useEventListener/useEventListener"

export default function useWindowSize() {
    const [windowSize, setWindowSize] = useState({
        width: window.innerWidth,
        height: window.innerHeight,
    })
    useEventListener("resize", () => {
        setWindowSize({ width: window.innerWidth, height: window.innerHeight })
    })
    return windowSize
}

useWindowSize 的主要优点之一是它的易用性。通过简单地导入钩子并在功能组件中调用它,您就可以访问包含窗口当前宽度和高度的对象。这消除了对样板代码的需要,并允许您专注于构建动态和响应式界面。

useEventListener 钩子也包含在这个包中,它可以智能地侦听窗口大小调整事件。每当窗口大小发生变化时,useWindowSize 都会使用最新尺寸更新状态,从而触发使用组件的重新渲染。这可以保证您的 UI 与用户的查看环境保持同步,从而带来更加身临其境和完美的用户体验。

useWindowSize 挂钩可用于多种场景。在构建适应不同屏幕尺寸的响应式布局时,它特别方便。使用此挂钩,您可以根据可用的窗口空间轻松调整组件的样式、布局或内容。此外,它使您能够动态渲染或隐藏元素、优化图像加载或执行依赖于窗口尺寸的任何其他行为。

import useWindowSize from "./useWindowSize"

export default function WindowSizeComponent() {
    const { width, height } = useWindowSize()
    return (
        <div>
            {width} x {height}
        </div>
    )
}