【React】万字长文!100+个让你事半功倍的常用 React Hooks 和工具包

简介: 【React】万字长文!100+个让你事半功倍的常用 React Hooks 和工具包

微信图片_20230612160734.png

一、关于 React Hooks

我问ChatGPTWhy Do We Need React Hooks?

ChatGPT说:

React Hooks are a new feature in React 16.8 that provide a way to access state and other React features without writing a class. They allow developers to use state and other React features, such as lifecycle methods, in functional components instead of writing class components. This helps keep code concise and maintainable, and makes it easier to reuse stateful logic between components.

Hooks also allow for a better overall code structure, since all the logic related to a component is grouped together in the same file. This makes it easier to navigate and understand the codebase, and allows for better and faster refactoring. Additionally, hooks allow for better performance in programs with many components, since they allow developers to share logic between components without having to create new instances of classes.

React HooksReact 16.8 中的一项新功能,它提供了一种无需编写类即可访问状态和其他 React 功能的方法。 它们允许开发人员在功能组件中使用状态和其他 React 功能,例如生命周期方法,而不是编写类组件。这有助于保持代码的简洁性和可维护性,并使在组件之间重用有状态逻辑变得更加容易。

钩子还允许更好的整体代码结构,因为与组件相关的所有逻辑都集中在同一个文件中。这使得导航和理解代码库变得更加容易,并允许更好更快地进行重构。 此外,钩子允许在具有许多组件的程序中获得更好的性能,因为它们允许开发人员在组件之间共享逻辑而无需创建类的新实例。

这是使用 useState 挂钩创建简单计数器的示例:

tsx

复制代码

import React, { useState } from 'react';
function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

你觉得 ChatGPT 这个回答你满意吗?

关于React Hooks,推荐阅读:

  1. Introducing Hooks - React
  2. React Hooks 初学者指南
  3. Making Sense of React Hooks —— Dan 大神的文章
  4. 6 Reasons to Use React Hooks Instead of Classes

二、常用自定义 React Hooks

下面列举一些比较常用的React Hooks及其实现思路。

PS:会不断迭代更新,当做工具库来使用~

1、useWindowSize

比如,在某些场景下需要获取用户窗口的宽度和高度(特别是在实现响应式设计的时候),就可以使用下面这个Hooks:

import { useState, useEffect } from "react";
const useWindowSize = () => {
  const getWindowSize = () => ({
    innerHeight: window.innerHeight,
    innerWidth: window.innerWidth,
    outerHeight: window.outerHeight,
    outerWidth: window.outerWidth,
  })
  const [windowSize, setWindowSize] = useState(getWindowSize())
  const handleResize = () => {
    setWindowSize(getWindowSize())
  }
  useEffect(() => {
    window.addEventListener('resize', handleResize)
    return () => {
      window.removeEventListener('resize', handleResize)
    }
  }, [])
  return windowSize
}

用法示例:

import { useWindowSize } from './hooks'
function App() {
  const windowSize = useWindowSize()
  return (
    <div>
      <ul>
        <li>window inner width: {windowSize.innerWidth}</li>
        <li>window inner height: {windowSize.innerHeight}</li>
        <li>window outer width: {windowSize.outerWidth}</li>
        <li>window outer height: {windowSize.outerHeight}</li>
      </ul>
    </div>
  )
}

2、useKeyPress

这个钩子可以检测何时按下特定键,可以根据按下的键触发事件。例如,关闭模态框、提交表单等键盘行为:

import { useState, useEffect } from "react";
const useKeyPress = (targetKey) => {
  const [keyPressed, setKeyPressed] = useState(false);
  const downHandler = ({ key }) => {
    if (key === targetKey) {
      setKeyPressed(true);
    }
  };
  const upHandler = ({ key }) => {
    if (key === targetKey) {
      setKeyPressed(false);
    }
  };
  useEffect(() => {
    window.addEventListener("keydown", downHandler);
    window.addEventListener("keyup", upHandler);
    return () => {
      window.removeEventListener("keydown", downHandler);
      window.removeEventListener("keyup", upHandler);
    };
  }, []);
  return keyPressed;
};

用法示例:

const closeModalKeyPress = useKeyPress("Escape");

3、useInterval

setInterval 函数一样,这个 hooks 有很多用途,比如动画、定期更新数据,甚至设置计时器:

ts

复制代码

import { useState, useEffect, useRef } from "react";
const useInterval = (callback, delay) => {
  const savedCallback = useRef();
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);
  useEffect(() => {
    function tick() {
      savedCallback.current && savedCallback.current();
    }
    if (delay !== null && delay > 0) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    } else {
      tick();
    }
  }, [delay]);
};

用法示例:

const [count, setCount] = useState(0);
useInterval(() => {
  setCount(count + 1);
}, 1000);

4、useDebounce

这是一个防抖的 hooks,主要用来限制 API 调用频次或输入时的状态更新,特别是在搜索输入中输入文本时:

import { useState, useEffect, useRef } from "react";
const useDebounce = (callback, delay) => {
  const [debouncing, setDebouncing] = useState(false);
  const savedCallback = useRef<() => void>();
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);
  useEffect(() => {
    if (debouncing) {
      return;
    }
    savedCallback.current && savedCallback.current();
    setDebouncing(true);
    setTimeout(() => {
      setDebouncing(false);
    }, delay);
  }, [debouncing, delay]);
};

用法示例:

const [inputValue, setInputValue] = useState("");
useDebounce(() => {
    // do somethings
}, 500);

5、useThrottle

上一个是debounce hook,怎么能少了 throttle hook

顾名思义,它是一个用来限制函数的钩子,将每隔指定的时间间隔执行一次。对于防止快速连续触发过多的 API 调用或事件很有用:

import { useState, useEffect, useRef } from "react";
const useThrottle = (callback, limit) => {
  const [throttling, setThrottling] = useState(false);
  const savedCallback = useRef();
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);
  useEffect(() => {
    function execute() {
      savedCallback.current && savedCallback.current();
      setThrottling(true);
      setTimeout(() => {
        setThrottling(false);
      }, limit);
    }
    if (!throttling) {
      execute();
    }
  }, [throttling, limit]);
};

用法示例:

const [inputValue, setInputValue] = useState("");
useThrottle(() => {
// do somethings
}, 500);

6、useToggle

这是一个用于管理、切换状态的钩子。例如,这样的钩子对于创建可折叠组件很有用。它可以帮助你检查当前的切换状态并在“开”和“关”状态之间切换。它还可以允许重置状态或手动设置。

实现很简单,使用 useState 钩子来存储切换状态。除此之外,它还有两个功能,handleResethandleTogglehandleReset会将切换状态重置为初始值。将handleToggle反转当前的切换状态。它从“开”切换到“关”,反之亦然。

import { useState } from 'react'
const useToggle = (initialState = false) => {
  const [toggle, setToggle] = useState(initialState)
  const handleReset = () => setToggle(initialState)
  const handleToggle = () => setToggle(prevState => !prevState)
  return {
    on: toggle,
    set: setToggle,
    reset: handleReset,
    toggle: handleToggle
  }
}

用法示例:

import { useToggle } from './hook'
function App() {
  const { on, set, reset, toggle } = useToggle()
  return (
    <div>
      <p>On: {on ? 'true' : 'false'}</p>
      <button onClick={() => set(true)}>Set to on</button>
      <button onClick={reset}>Reset</button>
      <button onClick={toggle}>Toggle</button>
    </div>
  )
}

7、useLocalStorage

这个钩子可以将数据存储在本地。接收两个参数:要存储的key和该键的初始值。

import { useState } from 'react'
const useLocalStorage = (keyName, initialValue) => {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      if (typeof window === 'undefined') {
        return initialValue
      }
      const item = window.localStorage.getItem(keyName)
      return item !== null ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log(error)
      return initialValue
    }
  })
  const setValue = (value) => {
    try {
      setStoredValue(value)
      window.localStorage.setItem(keyName, JSON.stringify(value))
    } catch (error) {
      console.log(error)
    }
  }
  return [storedValue, setValue]
}

使用与 useState 类似:

import { useLocalStorage } from './hooks'
function App() {
  const [value, setValue] = useLocalStorage('name', 'Joe')
  return (
    <div>
      <p>{value}</p>
      <input type="text" onChange={(e) => setValue(e.currentTarget.value)} />
    </div>
  )
}

升级版 useLocalStorage

import { useCallback, useState, useEffect } from "react"
const useLocalStorage = (key, defaultValue) => {
  return useStorage(key, defaultValue, window.localStorage)
}
const useSessionStorage = (key, defaultValue) => {
  return useStorage(key, defaultValue, window.sessionStorage)
}
const 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]
}

useLocalStorageuseSessionStorage :组件将值存储在浏览器的LocalStorageSessionStorage中,并使其与组件的状态保持同步。

useLocalStorageuseSessionStorage功能类似,但分别使用不同的存储localStoragesessionStorage。他们接受两个参数:keydefaultValuekey是用于在存储对象中存储值的键,是在defaultValue存储对象中找不到该键时将使用的值。

这两个函数都使用storage函数,它接受三个参数:key, defaultValuestorageObject并返回一个包含三个元素的数组:

  • 当前值
  • 可用于更新状态和存储中的值的函数“setValue”。
  • 可用于从状态和存储中删除值的“删除”函数。

useEffect 使存储在浏览器存储器中的值与组件的状态保持同步。 该useStorage函数使用JSON.stringify()JSON.parse()方法在将值存储在存储对象中时将值转换为JSON字符串,并在从存储对象中检索值时将其转换回 JavaScript 对象。这样一来,钩子可以处理任何数据,而不仅仅是字符串。

用法示例:


import { useSessionStorage, useLocalStorage } from "./useStorage"
function StorageComponent() {
  const [name, setName, removeName] = useSessionStorage("name", "Kyle")
  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>
  )
}

在跨浏览器会话或页面持久保存数据并使数据与组件状态保持同步的情况下,此钩子很有用。例如,您可以存储用户设置、表单数据或待办事项列表。使用 useLocalStorageuseSessionStorage 挂钩可以根据需要灵活地使用浏览器的本地存储或会话存储。

8、useEventListener

实现一

这个自定义 React 钩子负责在组件挂载时设置事件侦听器,并在组件卸载时移除侦听器。

先来看一下addEventListenerremoveEventListener函数定义,这样我们就可以看到我们需要将哪些参数传递给自定义挂钩,以使其对所有用例都灵活:


target.addEventListener(type, listener[, options]);
target.removeEventListener(type, listener[, options]);

从定义中,我们可以看出这两个函数都使用以下参数:targettypelisteneroptions。现在我们已经建立了输入参数,我们可以继续创建我们的钩子

import { useEffect } from 'react';
const useEventListener = (target, type, listener, ...options) => {
  useEffect(
    () => {
      target.addEventListener(type, listener, ...options);
      return () => {
        target.removeEventListener(type, listener, ...options);
      };
    },
    [target, type, listener, options]
  );
};

我们使用useEffectReact hook 来处理组件挂载和卸载时发生的事情。使用:

useEventListener(window, 'resize', handleWindowResize);

如果我们传递一个document或者window,这个钩子会很好地工作。但是,如果我们打算传递 ref,这个自定义可以进一步改进为:

import { useEffect } from 'react';
const useEventListener = (target, type, listener, ...options) => {
  useEffect(
    () => {
      const targetIsRef = target.hasOwnProperty("current");
      const currentTarget = targetIsRef ? target.current : target;
      if (currentTarget) {
          currentTarget.addEventListener(type, listener, ...options);
      }
      return () => {
        if (currentTarget) {
            currentTarget.removeEventListener(type, listener, ...options);
        }
      };
    },
    [target, type, listener, options]
  );
};

在useEffect钩子内部,我们检查传递的是ref还是 windowdocument

用法示例(我们可以在同一组件中根据需要初始化任意数量的侦听器):

// Window resize listener
useEventListener(window, 'resize', handleWindowResize);
// 也可以传ref
const elementRef = useRef(null);
useEventListener(elementRef, 'mousedown', handleElementClick);

实现二

import { useEffect, useRef } from "react"
const 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 允许组件向特定的 DOM 元素添加事件监听器,并在事件发生时执行回调函数。

钩子接受三个参数:

  • eventType:表示要侦听的事件类型,例如“click”“keydown”
  • callback:表示事件发生时要执行的操作。
  • element:是一个可选的DOM元素,用于添加事件监听器。默认值为window,这意味着事件监听器将被添加到全局 window 对象。

它还创建一个 ref calledcallbackRef来存储当前的回调函数。useEffect 用于在组件挂载时设置事件侦听器,并在组件卸载时移除事件侦听器。当回调函数发生变化时,它还会更新回调 ref

下面是一个如何使用这个钩子的例子:


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

如果你希望以声明方式处理单击、按键或表单提交等事件,并使组件逻辑与事件处理逻辑分离,则此挂钩很有用。

9、useRoveFocus

当处理列表时,你可能希望使用箭头在列表项之间移动焦点。当焦点到达底部时,你可能还想将焦点返回到顶部。可以使用称为Roving focus的技术来实现此目的。

当想点击列表中的下一个元素时,将下一个元素的tabIndex设为0,将所有其他列表项的tabIndex设为-1,然后在该列表项的 ref 上调用ref.current.focus()。然后还需要维护列表的大小和当前获得焦点的项目的索引,以便我们可以知道下一个要关注的元素。

我们可以用一个自定义Hooks来实现:

import { useCallback, useState, useEffect } from "react";
const useRoveFocus = (size) => {
  const [currentFocus, setCurrentFocus] = useState(0);
  const handleKeyDown = useCallback(
    e => {
      if (e.keyCode === 40) {
        e.preventDefault();
        setCurrentFocus(currentFocus === size - 1 ? 0 : currentFocus + 1);
      } else if (e.keyCode === 38) {
        e.preventDefault();
        setCurrentFocus(currentFocus === 0 ? size - 1 : currentFocus - 1);
      }
    },
    [size, currentFocus, setCurrentFocus]
  );
  useEffect(() => {
    document.addEventListener("keydown", handleKeyDown, false);
    return () => {
      document.removeEventListener("keydown", handleKeyDown, false);
    };
  }, [handleKeyDown]);
  return [currentFocus, setCurrentFocus];
}

用法示例:

import React, { useEffect, useRef, useCallback } from "react";
const Item = ({ character, focus, index, setFocus }) => {
  const ref = useRef(null);
  useEffect(() => {
    if (focus) {
      ref.current.focus();
    }
  }, [focus]);
  const handleSelect = useCallback(() => {
    alert(`${character}`);
    setFocus(index);
  }, [character, index, setFocus]);
  return (
    <li
      tabIndex={focus ? 0 : -1}
      role="button"
      ref={ref}
      onClick={handleSelect}
      onKeyPress={handleSelect}
    >
      {character}
    </li>
  );
};
export default Item;
import React from "react";
import Item from "./Item";
import useRoveFocus from "./useRoveFocus";
const characters = [
  "hello world",
  "hello ian",
  "hello kevin"
];
const List = () => {
  const [focus, setFocus] = useRoveFocus(characters.length);
  return (
    <ul>
      {characters.map((character, index) => (
        <Item
          key={character}
          setFocus={setFocus}
          index={index}
          focus={focus === index}
          character={character}
        />
      ))}
    </ul>
  );
};

由于我们可能希望在单击元素时更改焦点,因此我们将setFocus函数从钩子传递给Item组件来改变焦点:

import React, { useEffect, useRef, useCallback } from "react";
const Item = ({ character, focus, index, setFocus }) => {
  const ref = useRef(null);
  useEffect(() => {
    if (focus) {
      ref.current.focus();
    }
  }, [focus]);
  const handleSelect = useCallback(() => {
    alert(`${character}`);
    setFocus(index);
  }, [character, index, setFocus]);
  return (
    <li
      tabIndex={focus ? 0 : -1}
      role="button"
      ref={ref}
      onClick={handleSelect}
      onKeyPress={handleSelect}
    >
      {character}
    </li>
  );
};

10、useBoolean

这个钩子的实现是这样的:

const useBoolean = (initialValue) => {
    const [value, setValue] = useState(initialValue)
    const updateValue = useRef({
        toggle: () => setValue(oldValue => !oldValue),
        on: () => setValue(true),
        off: () => setValue(false)
    })
    return [value, updateValue.current]
}

用法示例:

const Articles = () => {
    const [articles, setArticles] = useState([])
    const [isLoading, setIsLoading] = useBoolean(false)
    const [isError, setIsError] = useBoolean(false)
    useEffect(() => {
        setIsLoading.on()
        fetch(...)
            .then(res => res.json())
            .then(setArticles)
            .catch(setIsError.on)
            .finally(setIsLoading.off)
  }, [])
    return ...

11、useTimeout

钩子实现:

import { useCallback, useEffect, useRef } from "react"
const 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 }
}
  • 钩子有两个参数:一个是回调函数,将在指定的延迟后调用;一个是延迟时间是调用回调之前应该经过的时间(以毫秒为单位)。
  • 返回一个具有两个属性的对象:resetclear,可用于重置或清除超时的函数。

这个钩子使用 useRef 创建两个引用:callbackReftimeoutRefcallbackRef 将回调函数作为可变值保存,而 timeoutRef 包含 setTimeout() 函数返回的超时 ID。

useEffect 用于确保callbackRef.current始终传递最新的回调。 该函数使用setTimeout创建一个新的超时,在指定的延迟时间后调用回调函数。clear函数使用clearTimeout清除超时。然后还有另一个钩子用于设置挂载超时并在卸载时将其删除。reset函数是clearset函数的组合。最后,useCallback 钩子确保函数仅在它们的依赖发生变化时才重新创建。

用法示例:

import { useState } from "react"
import useTimeout from "./useTimeout"
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>
  )
}

这个自定义useTimeout钩子在组件需要充当不可避免的延迟的各种情况下很有用。例如:

  • 一定时间后让通知消息消失
  • 表单提交:在重定向之前显示loading spinner
  • 在一定时间后自动前进到下一张slide
  • 在零时触发动作的倒数计时器显示剩余时间
  • 表单数据的自动保存功能
  • 用户失活的时候注销会话
  • 延迟回调执行防抖函数

12、useUpdateEffect

钩子实现:

import { useEffect, useRef } from "react"
const useUpdateEffect = (callback, dependencies) => {
  const firstRenderRef = useRef(true)
  useEffect(() => {
    if (firstRenderRef.current) {
      firstRenderRef.current = false
      return
    }
    return callback()
  }, depen

useUpdateEffect :组件仅在特定依赖项发生变化时运行回调函数。

钩子接受两个参数:

  1. callback:是依赖项更改时应调用的函数
  2. dependencies:是一个值数组,钩子监听这些值的变化

钩子使用useRef钩子创建一个firstRenderRef初始值为true的引用。该引用将用于跟踪组件的第一次渲染。useEffect hook 用于监听 dependencies 数组的变化并调用回调函数。在useEffect函数内部,它通过检查firstRenderRef值来检查这是否是组件的第一次渲染。如果是,则将其设置为 false 并返回。如果不是,说明这是一次更新,所以会调用回调函数,返回回调函数。

用法示例:

import { useState } from "react"
import useUpdateEffect from "./useUpdateEffect"
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>
  )
}

如果你只想在特定值更改时而不是在初始渲染时运行某些逻辑,此钩子会很有用。例如,当你希望在用户从下拉菜单中选择特定选项后从 API 获取数据时,或者当你希望在窗口大小更改后更新元素在屏幕上的位置时。

13、useArray

钩子实现:

import { useState } from "react"
const 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用来管理数组状态。该钩子接受一个参数 ,defaultValue用于初始化数组状态。该钩子返回一个具有多个属性的对象:

  • array:当前数组
  • set:将数组设置为新值
  • push:将一个元素添加到数组的末尾
  • filter:传递回调函数来过滤数组
  • update:更新数组特定索引处的元素
  • remove:删除数组特定索引的元素
  • clear:清空数组

使用 setArray 函数,通过创建新数组、添加或删除元素,然后将其传递给 setArray 函数来保持状态的不变性。

用法示例:

import useArray from "./useArray"
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>
  )
}

如果您想管理组件状态下的数据数组并执行日常数组操作(例如添加、删除、更新和过滤元素),此钩子很有用。

14、usePrevious

import { useRef } from "react"
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 跟踪组件变量的先前值。

钩子接受一个参数value,它是变量的当前值。然后,它创建两个引用,一个称为currentRef,其中保存变量的当前值,另一个称为previousRef,其中包含变量的先前值。

挂钩将当前值与先前值进行比较。如果不同,它将使用当前值更新 previousRef 并使用新值更新 currentRef。然后它返回previousRef.current

用法示例:

import { useState } from "react"
import usePrevious from "./usePrevious"
function PreviousComponent() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState("Kyle")
  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>
  )
}

此挂钩在需要访问变量的先前值的情况下很有用,例如,当您想要将当前值与之前的值进行比较以检查它是否已更改或当您想要跟踪更改时随时间变化的变量。

15、useStateWithHistory

import { useCallback, useRef, useState } from "react"
const 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. defaultValue是状态的初始值
  2. capacity是一个可选参数,用于设置应存储在历史记录中的最大状态数。

该钩子创建了两个引用,一个被称为historyRef保存状态历史数组,另一个被称为pointerRef具有历史的当前指针。它还创建了三个回调函数:setbackforward

  • set函数用于设置状态,它的工作方式与内置setState函数类似,但它还通过将新值添加到历史数组并更新 pointerRef 来跟踪状态的历史记录。该函数可以接受一个值或一个接收当前状态作为参数的回调函数。该函数还通过删除最旧的元素来确保不超过历史数组的容量。
  • back函数导航历史记录中的先前状态。它递减pointerRef并使用历史数组的较早值更新状态。
  • forward函数导航历史记录中的下一个状态。它递增 pointerRef 并使用历史数组中的下一个值更新状态。
  • go函数导航历史记录中的特定状态。它将 pointerRef 设置为作为参数传递的索引,并使用历史数组中该索引处的值更新状态。

用法示例:

import { useState } from "react"
import useStateWithHistory from "./useStateWithHistory"
function StateWithHistoryComponent() {
  const [count, setCount, { history, pointer, back, forward, go }] =
    useStateWithHistory(1)
  const [name, setName] = useState("Kyle")
  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>
  )
}

这个钩子在你想要跟踪状态历史记录的情况下很有用,例如,当您想要实现撤消或重做功能或允许用户浏览更改历史记录时。

16、useAsync

import { useCallback, useEffect, useState } from "react"
const 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 处理组件异步操作并跟踪loadingerrorvalue状态。

钩子接受两个参数:

  • callback:是一个返回Promise的函数,该函数负责执行异步操作。
  • dependencies:是钩子应该侦听更改的依赖项数组。回调函数将在任何依赖项更改时执行。

该钩子返回三个状态变量:loadingerrorvalue。加载状态用于指示异步操作当前是否正在进行,错误状态用于在Promisereject的情况下存储错误对象,而值状态用于在Promise 成功返回的情况下存储已解析的值.

该挂钩还使用 useCallback 创建了一个名为 callbackMemoized 的回调函数。该函数将loadingerrorvalue状态设置为初始值,然后调用传入的回调函数。 useEffect 在依赖项发生变化时调用 callbackMemoized 函数。当useEffect依赖项发生变化时,钩子调用 callbackMemoized 函数。

用法示例:

import useAsync from "./useAsync"
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>
  )
}

此钩子在处理异步操作(例如从 API 获取数据、上传文件或将数据保存到数据库)的情况下非常有用。它提供了一种简单的方法来管理组件中的加载、错误和成功返回值的状态,还允许组件在某些值更改时重新运行异步操作。

17、useFetch

import useAsync from "./useAsync"
const DEFAULT_OPTIONS = {
  headers: { "Content-Type": "application/json" },
}
const 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允许组件处理从 URL 获取数据并跟踪loadingerrorvalue状态。它使用内置的获取API和自定义挂钩useAsync,允许组件处理异步操作并跟踪loadingerrorvalue状态。。

该钩子接受三个参数:

  • URL:要从中获取数据的endpointURL
  • options:是一个包含选项的对象,例如获取请求的headersmethodbody
  • dependencies:是钩子应该侦听更改的依赖项数组。回调函数将在任何依赖项更改时执行。

该钩子返回一个回调函数,该函数使用 fetch API 来请求具有传入选项和默认选项的给定URL 。 然后检查响应是否正常。如果是,它以 json 格式返回响应。如果不是,它返回 JSON 响应并拒绝它。

用法示例:

import { useState } from "react"
import useFetch from "./useFetch"
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>
  )
}

在处理从 API 获取数据的情况下,此钩子会很有用。它提供了一种简单的方法来管理组件中的加载、错误和值状态。

18、useScript

import useAsync from "./useAsync"
const 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 从给定的 URL 加载 JavaScript 文件并跟踪加载、错误和值状态。此外,它还使用useAsync允许组件处理异步操作并跟踪加载、错误和值状态的自定义挂钩。

钩子接受一个参数:

  • URL:要加载的 JavaScript 文件的 URL

该钩子返回一个回调函数,该函数使用 DOM API 创建一个新的脚本元素并将其 src 设置为传入的 URL。它还将该async属性设置为 true。然后它返回一个新的Promise,当脚本加载或出错时分别resolves 或者 rejects

用法示例:

import useScript from "./useScript"
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>
}

这个钩子在你想要动态加载外部 JavaScript 库的情况下很有用。它提供了一种管理组件的加载、错误和值状态的简单方法,还允许组件在 URL 更改时重新加载脚本。

19、useDeepCompareEffect

import { useEffect, useRef } from "react"
import isEqual from "lodash/fp/isEqual"
const useDeepCompareEffect = (callback, dependencies) => {
  const currentDependenciesRef = useRef()
  if (!isEqual(currentDependenciesRef.current, dependencies)) {
    currentDependenciesRef.current = dependencies
  }
  useEffect(callback, [currentDependenciesRef.current])
}

useDeepCompareEffect允许组件仅在依赖关系发生变化时使用深度比较而不是浅层比较来运行效果。

钩子有两个参数:

  • callback:是一个函数,表示被执行的回调。
  • dependencies:是回调所依赖的值数组。

它还会创建一个 ref 调用currentDependenciesRef来存储当前的依赖项。然后它使用该函数isEqual将当前依赖项与新依赖项进行比较。如果它们不相等,它会用新的依赖项更新当前的依赖项 ref。 然后它调用带有回调函数的 useEffectcurrentDependenciesRef.current 作为依赖项。

用法示例:

import { useEffect, useState, useRef } from "react"
import useDeepCompareEffect from "./useDeepCompareEffect"
function DeepCompareEffectComponent() {
  const [age, setAge] = useState(0)
  const [otherCount, setOtherCount] = useState(0)
  const useEffectCountRef = useRef()
  const useDeepCompareEffectCountRef = useRef()
  const person = { age: age, name: "Kyle" }
  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>
  )
}

此挂钩在依赖项是复杂对象或数组的情况下很有用,并且如果你希望确保效果仅在依赖项中的特定值发生更改时运行。它可以帮助防止不必要的重新渲染并提高性能。

20、useOnScreen

import { useEffect, useState } from "react"
const 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 允许组件检测特定DOM元素何时在视口中可见并跟踪可见性状态。它使用 useEffectIntersectionObserver API实现。

钩子有两个参数:

  • ref是对 DOM 元素的引用以检测可见性,通常使用 useRef创建。
  • rootMargin是一个可选字符串,用于定义根元素周围的偏移量。它可用于在检查交叉点之前放大或缩小根元素的边界框。默认值为“0px”

useEffect 用于在组件挂载时设置IntersectionObserver并在组件卸载时移除观察者。它还会在 refrootMargin 更改时更新观察者。它返回一个布尔值isVisible,指示 DOM 元素当前是否可见。

用法示例:

import { useRef } from "react"
import useOnScreen from "./useOnScreen"
function OnScreenComponentComponent() {
  const headerTwoRef = useRef()
  const visible = useOnScreen(headerTwoRef, "-100px")
  return (
    <div>
      <h1>Header</h1>
      <div>
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Unde incidunt,
        nam id itaque error dicta? Numquam earum iusto optio officia, molestias
        debitis illum facilis nemo asperiores eaque voluptates modi? Dicta
        mollitia fugit doloremque vitae, dolores sequi fuga quas vel incidunt
        animi architecto dignissimos amet in quam praesentium corrupti voluptate
        dolorem impedit numquam aut cupiditate nulla! Nisi dolore dicta, cumque
        illum tempora enim dolores eum quis itaque nostrum architecto vel cum
        officiis aperiam qui exercitationem voluptatibus. Veritatis unde
        doloribus dolorem architecto, eum reprehenderit possimus similique eius
        cum obcaecati totam placeat. Delectus nulla, quae temporibus omnis
        assumenda autem ad quibusdam facilis aspernatur inventore nobis. Vitae
        architecto, unde consequuntur velit consequatur dicta mollitia, fuga
        iure hic accusamus blanditiis. Dignissimos, tenetur amet adipisci
        nostrum perferendis ad rerum accusamus distinctio repellendus eius,
        quisquam repellat nesciunt, consequatur culpa neque? Inventore vitae
        laborum aperiam ullam dolorem officiis ipsum aliquid doloribus pariatur,
        commodi iure illum soluta delectus, architecto ratione maiores
        accusamus. Provident quia sequi dolorum asperiores necessitatibus
        consequatur perspiciatis at a, inventore, deserunt corporis recusandae
        earum vero voluptas saepe pariatur, libero illo. Numquam facilis magnam
        exercitationem ipsam libero quidem minima dolores perferendis eveniet
        impedit eos, nesciunt unde velit facere itaque eum quasi laboriosam
        veritatis aliquid tenetur. Blanditiis exercitationem laborum, optio
        nulla minima libero sed doloremque soluta, dignissimos tempora rerum id
        nostrum iusto eveniet illo corrupti dicta. Non fuga exercitationem sit
        dignissimos voluptatibus cumque nobis iste asperiores illum fugit
      </div>
      <h1 ref={headerTwoRef}>Header 2 {visible && "(Visible)"}</h1>
      <div>
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Unde incidunt,
        nam id itaque error dicta? Numquam earum iusto optio officia, molestias
        debitis illum facilis nemo asperiores eaque voluptates modi? Dicta
        mollitia fugit doloremque vitae, dolores sequi fuga quas vel incidunt
        animi architecto dignissimos amet in quam praesentium corrupti voluptate
        dolorem impedit numquam aut cupiditate nulla! Nisi dolore dicta, cumque
        illum tempora enim dolores eum quis itaque nostrum architecto vel cum
        officiis aperiam qui exercitationem voluptatibus. Veritatis unde
        doloribus dolorem architecto, eum reprehenderit possimus similique eius
        cum obcaecati totam placeat. Delectus nulla, quae temporibus omnis
        assumenda autem ad quibusdam facilis aspernatur inventore nobis. Vitae
        architecto, unde consequuntur velit consequatur dicta mollitia, fuga
        iure hic accusamus blanditiis. Dignissimos, tenetur amet adipisci
        nostrum perferendis ad rerum accusamus distinctio repellendus eius,
        quisquam repellat nesciunt, consequatur culpa neque? Inventore vitae
        laborum aperiam ullam dolorem officiis ipsum aliquid doloribus pariatur,
        commodi iure illum soluta delectus, architecto ratione maiores
        accusamus. Provident quia sequi dolorum asperiores necessitatibus
        consequatur perspiciatis at a, inventore, deserunt corporis recusandae
        earum vero voluptas saepe pariatur, libero illo. Numquam facilis magnam
        exercitationem ipsam libero quidem minima dolores perferendis eveniet
        impedit eos, nesciunt unde velit facere itaque eum quasi laboriosam
        veritatis aliquid tenetur. Blanditiis exercitationem laborum, optio
        nulla minima libero sed doloremque soluta, dignissimos tempora rerum id
        nostrum iusto eveniet illo corrupti dicta. Non fuga exercitationem sit
        dignissimos voluptatibus cumque nobis iste asperiores illum fugit
        veritatis fugiat quia voluptates cupiditate vel rerum eligendi facere
        sint nostrum quam, maiores dolorem repellat voluptas! Magnam ullam quis
        quas aut consequuntur quo doloremque, earum sint soluta vero iste quasi
        voluptates labore rerum aspernatur illum esse maxime laudantium? Tempore
        perspiciatis perferendis ea dolorem et quasi eos illo beatae consectetur
        maxime, enim ducimus corrupti, accusantium quisquam rem dolorum itaque
        iste velit. Amet similique accusamus doloribus expedita modi a
        architecto accusantium labore unde non, dolore totam quaerat sit
        laboriosam quae ullam impedit, pariatur repudiandae quisquam debitis
        repellendus nihil. Cumque blanditiis ut recusandae illum! Maiores
        eveniet nulla exercitationem natus delectus est minus a architecto
        pariatur molestias quo nihil maxime quasi facere magnam neque dolorem
        ad, doloribus hic! Qui corporis perspiciatis dolores rem minima tenetur.
        Fugit ipsa consectetur ad reiciendis, quia iste, sapiente rerum
        exercitationem reprehenderit laborum eligendi cumque? Quia porro modi
        repudiandae nostrum accusamus! Corporis eum fugit nihil facilis placeat
        ab est obcaecati consequuntur qui atque tempore soluta aliquid saepe
        ducimus, at sed modi illo ipsa numquam ratione vero eos reprehenderit!
        Sapiente nesciunt consequatur labore iste quas possimus rem cumque,
        fugit laborum repellendus nisi adipisci officia temporibus quaerat!
        Beatae doloribus veritatis at, maiores suscipit debitis reiciendis cum
        impedit non aut modi iste? Placeat illo quisquam assumenda esse cum
        ipsum quasi perspiciatis voluptatem rerum itaque, similique quidem
        molestias exercitationem ullam eum amet tempore dolor aliquid unde
        deserunt dolore excepturi. Aut dolore rerum sequi nihil soluta eum
        expedita consequatur aliquid consequuntur saepe esse necessitatibus
        repudiandae, natus, officia enim odit rem nobis adipisci, voluptates
        autem dolor blanditiis ipsam animi a. Illo accusantium iure qui aperiam
        commodi, quidem, dolorem error eum animi, id nam? Corporis, non
        adipisci!
      </div>
    </div>
  )
}

当你想要跟踪特定 DOM 元素何时进入视图或何时消失时,此挂钩会很有用,例如,延迟加载图像、跟踪滚动位置或按需显示元素。

21、useScroll

使用此挂钩,您将能够控制页面垂直方向的滚动并通过平滑滚动返回顶部/底部。这可以让你创建一个无限scroll(当isAtBottomtrue的时候fetch())。

钩子实现:

import { useEffect, useRef, useCallback, useState } from 'react'
const useScroll = ({ threshold = 450, isWindow = false, smooth = true } = {}) => {
  const [isAtBottom, setIsAtBottom] = useState(false)
  const ref = useRef(isWindow ? window : null)
  const goTop = useCallback(() => {
    const element = ref.current
    element.scrollTo({
      top: 0,
      behavior: smooth ? 'smooth' : 'auto'
    })
  }, [smooth])
  const goBottom = useCallback(() => {
    const element = ref.current instanceof Window ? document.documentElement : ref.current
    ref.current.scrollTo({
      top: element.scrollHeight,
      behavior: smooth ? 'smooth' : 'auto'
    })
  }, [smooth])
  const handleScroll = useCallback(() => {
    if (ref.current) {
      let isAtBottom
      if (ref.current instanceof Window) {
        const currentScrollTop = window.innerHeight + window.scrollY
        isAtBottom = currentScrollTop >= document.documentElement.offsetHeight - threshold
      } else {
        const currentScrollTop = ref.current.offsetHeight + ref.current.scrollTop
        isAtBottom = currentScrollTop >= ref.current.scrollHeight - threshold
      }
      setIsAtBottom(isAtBottom)
    }
  }, [threshold])
  useEffect(() => {
    if (isWindow) {
      window.addEventListener('scroll', handleScroll)
      return () => {
        window.removeEventListener('scroll', handleScroll)
      }
    }
  }, [isWindow, handleScroll])
  return { isAtBottom, handleScroll, goTop, goBottom, ref }
}

用法示例:

import React from 'react'
import Footer from './Footer'
import useScroll from './hooks'
function ScrollComponent({ children }) {
    const { isAtBottom, goTop } = useScroll({
       isWindow: true,
       threshold: 500
    })
    return (
        <>
          {isAtBottom && <Footer />}
          {children}
          <button onClick={goTop}>返回顶部</button>
        </>
    )
}

22、useMediaQuery

钩子实现:

import { useState, useCallback, useEffect } from 'react';
const useMediaQuery = (queries = [], values = [], defaultValue) => {
  const mediaQueryList = queries.map(q => window.matchMedia(q));
  const getValue = useCallback(() => {
    const index = mediaQueryList.findIndex(mql => mql.matches);
    return typeof values[index] !== 'undefined' ? values[index] : defaultValue;
  }, [mediaQueryList, values, defaultValue]);
  const [value, setValue] = useState(getValue);
  useEffect(() => {
    const handler = () => setValue(getValue);
    mediaQueryList.forEach(mql => mql.addEventListener('change', handler));
    return () =>
      mediaQueryList.forEach(mql => mql.removeEventListener('change', handler));
  }, [getValue, mediaQueryList]);
  return value;
};

这个钩子将帮助我们在我们的功能组件中以编程方式测试和监控媒体查询。例如,当您需要根据设备的类型或特定特征呈现不同的 UI 时。

钩子接受 3 个参数:

  • queries:媒体查询对应的字符串数组
  • values:匹配这些媒体查询的值数组,顺序与前一个数组相同
  • defaultValue:如果没有匹配的媒体查询,则为默认值 用法示例:
import { useMediaQuery } from './hooks';
function App() {
  const canHover = useMediaQuery(
    // 媒体查询
    ['(hover: hover)'],
    // 值对应上述媒体查询数组索引
    [true],
    // 默认值
    false
  );
  const canHoverClass = 'opacity-0 hover:opacity-100 transition-opacity';
  const defaultClass = 'opacity-100';
  return (
    <div className={canHover ? canHoverClass : defaultClass}>Hover me!</div>
  );
}

23、useDarkMode

钩子实现:

import { useEffect } from 'react';
import useMediaQuery from './useMediaQuery';
import useLocalStorage from './useLocalStorage';
const useDarkMode = () => {
  const preferDarkMode = useMediaQuery(
    ['(prefers-color-scheme: dark)'],
    [true],
    false
  );
  const [enabled, setEnabled] = useLocalStorage('dark-mode', preferDarkMode);
  useEffect(() => {
    if (enabled) {
      document.body.classList.add('dark');
    } else {
      document.body.classList.remove('dark');
    }
  }, [enabled]);
  return [enabled, setEnabled];
};
export default useDarkMode;

加上这个钩子,Tailwind CSS 成为在任何 React 程序中实现暗模式的最简单和最快的方法。

24、useTheme

import { atom, useAtom } from 'jotai';
import { useEffect, useLayoutEffect } from 'react';
export type TTheme = 'light' | 'dark';
const themeAtom = atom<TTheme>('light');
const useTheme = () => {
  // Media query
  const systemTheme: TTheme = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
  const localValue = localStorage.getItem('theme:type') as TTheme;
  const [theme, setTheme] = useAtom(themeAtom);
  useEffect(() => {
    setTheme(localValue || systemTheme);
  }, []);
  useLayoutEffect(() => {
    localStorage.setItem('theme:type', theme);
    document.body.dataset.theme = theme;
  }, [theme]);
  return [theme, setTheme] as const;
}

三、100 + react hooks 库

下面是一些非常优秀的React Hooks Tools Library:


ahooks:阿里开源,封装了大量好用的 Hooks

react-use

usehooks

beautiful-react-hooks

awesome-react-hooks

swr

react-hook-form

react-recipes

use-http

react-query

constate: 将本地状态提升到React Context

@fluentui/react-hooks

@rennalabs/hooks

@donisaac/react-hooks

@jam3/react-hooks

hookrouter

@restart/hooks

react-spring

@react-spring/core

@react-spring/shared

@react-spring/animated

@uifabric/react-hooks

Rhooks

react-hunger

react-use-scroll-indicator

useGameDice custom hook example

beautiful-react-hooks

30secondsofcode

usehooks & usehooks-typescript

@rest-hooks/hooks

@cara-care/react-event-hook

formik

@react-hook/window-size

tng-hooks

react-scroll-parallax

mantine-hooks

useHover

react-useasync-hooks

usePortal

useStorage

use-debounce

mobx-react-lite

react-responsive

react-cookie

use-deep-compare

use-deep-compare-effect

react-swipeable

use-query-params

react-script-hook

use-immer

use-context-selector

react-async-hook

use-ssr

use-places-autocomplete

react-cool-dimensions

react-use-clipboard

react-cool-onclickoutside

react-useportal

use-media

react-cool-inview

react-tracked

use-force-update

use-is-mounted-ref

easy-peasy

use-lilius

react-cool-virtual

react-media-hook

react-wait

use-dark-mode

react-hooks-global-state

use-react-router

graphql-hooks

react-use-form-state

redux-react-hook

react-countdown-hook

react-fetch-hook

use-undo

use-lang-direction

use-reducer-async

rxjs-hooks

use-double-click

react-use-scroll-position

react-metatags-hook

react-with-hooks

react-use-scrollspy

use-async-memo

use-sse

react-hooks-visible

react-cool-portal

react-darkreader

scroll-data-hook

use-events

react-speech-kit

react-use-trigger

use-position

the-platform

use-mouse-action

react-hooks-use-modal

react-hooks-worker

react-request-hook

react-intersection-visible-hook

use-overflow

@neogeek/common-react-hooks

use-abortable-fetch

react-window-communication-hook

use-clippy

react-hooks-async

use-multiselect

use-hovering

react-hooks-lib

react-screen-wake-lock

react-use-api

react-use-id-hook

fetch-suspense

use-scroll-to-bottom

modali

react-recaptcha-hook

use-email-autocomplete

react-hooks-svgdrawing

react-hookedup

use-detect-print

react-cached-callback

moment-hooks

use-click-away

react-cool-form

use-boolean

react-native-react-bridge

concent

react-context-refs

use-suspender

react-user-media


end ~


相关文章
|
8天前
|
前端开发 测试技术 开发工具
探索前端框架React Hooks的优势与应用
本文将深入探讨前端框架React Hooks的优势与应用。通过分析React Hooks的特性以及实际应用案例,帮助读者更好地理解和运用这一现代化的前端开发工具。
|
8天前
|
前端开发 JavaScript
react常用的hooks有哪些?
react常用的hooks有哪些?
20 0
|
8天前
|
前端开发 JavaScript UED
使用React Hooks优化前端应用性能
本文将深入探讨如何使用React Hooks来优化前端应用的性能,重点介绍Hooks在状态管理、副作用处理和组件逻辑复用方面的应用。通过本文的指导,读者将了解到如何利用React Hooks提升前端应用的响应速度和用户体验。
|
6天前
|
前端开发
React Hooks - useState 的使用方法和注意事项(1),web前端开发前景
React Hooks - useState 的使用方法和注意事项(1),web前端开发前景
|
7天前
|
前端开发 JavaScript
React Hooks:让你轻松掌握函数组件的状态与管理
React Hooks:让你轻松掌握函数组件的状态与管理
|
8天前
|
缓存 前端开发
Web开发:深入探讨React Hooks的使用和最佳实践
Web开发:深入探讨React Hooks的使用和最佳实践
18 0
|
8天前
|
存储 前端开发 JavaScript
React Hooks实战:从useState到useContext深度解析
React Hooks 深度解析:useState用于函数组件的状态管理,通过初始化和更新状态实现渲染控制;useContext则提供跨组件数据传递。useState的状态更新是异步的,不支持浅比较,可结合useEffect处理副作用。useContext在多层组件间共享状态,但可能导致不必要的渲染。两者结合可创建复杂应用场景,如带主题切换的计数器。了解其工作原理和优化策略,能有效提升React应用性能。
22 0
|
8天前
|
前端开发 API 开发者
React Hooks API:自定义Hooks的创建与使用
【4月更文挑战第25天】本文介绍了React自定义Hooks的创建与使用。自定义Hooks是提升React开发效率的关键工具。
|
8天前
|
前端开发
探索React Hooks:一种全新的组件逻辑管理方式
React Hooks是React 16.8版本引入的一项新功能,它改变了我们编写React组件的方式。本文将从Hooks的起源讲起,逐步分析Hooks的优势,并通过具体示例展示Hooks在组件逻辑管理中的应用,旨在帮助读者更好地理解和运用React Hooks。
|
8天前
|
前端开发 JavaScript
使用React Hooks实现简单的计数器应用
使用React Hooks实现简单的计数器应用

热门文章

最新文章