聊聊 React 日常研发最常用的 15个 Hooks

家好,很高兴又见面了,我是"高级前端?进阶?",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力。

在 React Hooks (React < 16.8) 之前,开发人员需要编写类组件才能利用某些 React 功能。 但现在,React Hooks 提供了一种更符合人体工程学的方式来构建组件,因为可以使用有状态逻辑而无需更改组件层次结构。

1. useState

useState 是最重要也是最常用的 Hooks。

useState Hook 的目的是处理反应性数据,应用程序中发生变化的任何数据都称为状态,当数据发生变化时,React 会重新渲染 UI。

const [count, setCount] = React.useState(0);

2. useEffect

useEffect Hook 允许开发者在函数组件中执行副作用,包括:

  • 从 API 获取数据
  • 更新 DOM
  • 订阅事件等
// 组件挂载和state数据变化都会调用,很显然第二个参数相当于完整的state
React.useEffect(() => {
  alert('Hey, Nads here!');
});

// 组件首次挂载调用
useEffect(() => {
  fetch('https://api.example.com/data')
    .then((response) => response.json())
    .then((data) => setData(data));
}, []);

// 当state中的count变化的时候调用
React.useEffect(() => {
  fetch('nads').then(() => setLoaded(true));
}, [count]);

// 当组件被销毁或者组件从界面移除之前调用
React.useEffect(() => {
  alert('Hey, Nads here');
  return () => alert('Goodbye Component');
});

3. useContext

该 Hooks 允许开发者使用 React 的 Context API,它本身是一种机制,允许在组件树中共享数据而无需通过 props,从而消除 prop-drilling:

const ans = {
  right: '?',
  wrong: '?',
};

const AnsContext = createContext(ans);
// 创建一个context
function Exam(props) {
  return (
    // 任何内部组件都可以使用context的值
    
      
    
  );
}

function RightAns() {
  //   从最近的父级Provider消费值
  const ans = React.useContext(AnsContext);
  return 

{ans}

; // 以前需要包裹在AnsContext.Consumer中,现在已经不需要了 }

4. useRef

该 Hooks 允许创建一个可变对象(Mutable Object)。 当值经常变化时可以使用,就像 useState Hook 一样,但不同的是,当值变化时不会触发重新渲染。

其常见用例是从 DOM 中获取 HTML 元素。

function App() {
  const myBtn = React.useRef(null);
  const handleBtn = () => myBtn.current.click();
  return ;
}

5. useReducer

功能与 setState 非常相似,是使用 Redux 模式管理状态的不同方式。

useReducer 不直接更新状态,而是 dispatch 动作,将其发送到 reducer 函数,然后该函数计算出下一个状态。

function reducer(state, dispatch) {
  switch (action.type) {
    case 'increment':
      return state + 1;
    case 'decrement':
      return state - 1;
    default:
      throw new Error();
  }
}
function useReducer() {
  // state is the state we want to show in the UI.
  const [state, dispatch] = React.useReducer(reducer, 0);
  return (
    <>
      Count : {state}
      
      
    
  );
}

6. useMemo

useMemo Hook 允许开发者存储一个值,以便仅在其依赖项发生变化时才重新计算。这可以通过防止不必要的重新计算来帮助提高性能,推荐在需要进行昂贵计算时使用。

function useMemo() {
  const [count, setCount] = React.useState(60);
  const expensiveCount = useMemo(() => {
    return count ** 2;
  }, [count]);
  // 当count的值变化时候重新计算expensiveCount的值
}

7. useCallback

useCallback Hook 允许记忆一个函数,以便仅在其依赖项发生变化时才重新创建,从而可以通过防止不必要的重新渲染来帮助提高性能。

以下是如何使用 useCallback 来记忆函数的示例:

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

function SearchBar({ onSearch }) {
  const [query, setQuery] = useState('');
  // onSearch的prop变化时候更新
  const handleQueryChange = useCallback(
    (event) => {
      setQuery(event.target.value);
      onSearch(event.target.value);
    },
    [onSearch]
  );

  return ;
}

在此示例中,定义了一个带有 onSearch prop 函数的 SearchBar 组件。使用 useCallback Hooks 来记忆 handleQueryChange 函数,以便仅在 onSearch 函数更改时才重新创建它。

8. useImperativeHandle

useImperativeHandle Hook 允许开发者自定义在使用 ref 时向父组件公开的实例值。 当需要向父组件提供某个接口,但又不想公开子组件的所有内部实现细节时,这可能很有用。

以下是如何使用 useImperativeHandle 的示例:

import React, { useRef, useImperativeHandle } from 'react';

const Input = React.forwardRef((props, ref) => {
  const inputRef = useRef();
  // 允许开发者自定义在使用 ref 时向父组件公开的实例值
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    value: inputRef.current.value,
  }));

  return ;
});

function App() {
  const inputRef = useRef();
  const handleClick = () => {
    inputRef.current.focus();
  };
  return (
    
{/*Input的ref传递下去*/}
); }

此示例定义了一个自定义 Input 组件,该组件在使用 ref 时使用 useImperativeHandle 向父组件公开焦点方法和 value 属性。 useImperativeHandle Hook 采用两个参数:ref 对象和回调函数,该函数返回一个对象,该对象具有应公开给父组件的属性和方法。

在 App 组件中使用 Input 组件并向其传递一个 ref 对象,还定义了一个 handleClick 函数,当单击按钮时,该函数调用 inputRef 对象的 focus 方法。

9. useLayoutEffect

工作原理与 useEffect Hook 相同,但有一点不同,回调将在渲染组件之后但在实际更新绘制到屏幕之前运行。 即,阻止视觉更新,直到回调完成。

function useLayoutEffectDemo() {
  const myBtn = React.useRef(null);

  React.useLayoutEffect(() => {
    const rect = myBtn.current.getBoundingClientRect();
    // scroll position before the dom is visually updated
    console.log(rect.height);
  });
}

10. useDebugValue

useDebugValue 是一个 Hook,允许开发者在 React DevTools 中显示自定义钩子的自定义调试信息。这对于调试 Hook 和了解幕后发生的事情很有用。

假设有 n 个使用相同逻辑的组件,那么可以单独定义自己的函数并且可以在其他组件中使用,但这里的关键是可以调试东西:

import { useState, useEffect, useDebugValue } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  useEffect(() => {
    fetch(url)
      .then((response) => response.json())
      .then((data) => setData(data));
  }, [url]);

  useDebugValue(data ? `Data loaded: ${data.length} items` : 'Loading...');

  return data;
}

在上面示例中定义了一个名为 useFetch 的自定义 Hook,用于从 URL 获取数据并返回。 使用 useDebugValue Hook 在 React DevTools 中显示自定义调试消息。 如果数据已加载,会显示一条消息,其中包含数据中的项目数。 如果数据仍在加载会显示一条消息“正在加载...”。

当在组件中使用 useFetch Hook 时,自定义调试消息将显示在 React DevTools 中。

11. useEffectEvent

上面说过,React 的 useEffect hook 用于在函数组件中执行副作用操作。而 useEffectEvent 是 React-use 库中提供的一个额外的 hook,是基于 useEffect 封装的一个事件监听器。

useEffectEvent 的主要场景是在函数组件中监听事件,这些事件包括:

  • Window 事件:resize、scroll、load、unload、beforeunload、popstate
  • Document 事件:mousemove、mousedown、mouseup、keypress、keydown、keyup、wheel
  • XMLHttpRequest 事件:loadstart、load、progress、abort、error、timeout、loadend
  • Custom 事件:自定义事件

useEffectEvent 与 useEffect 的区别在于,前者会自动移除事件监听器,而 useEffect 需要在返回函数中手动清除副作用。此外,useEffectEvent 还提供了一个回调函数,可以在事件发生时执行。

下面是一个 useEffectEvent 的使用示例:

import { useEffect, useState } from 'react';
import { useEffectEvent } from 'react-use';
function App() {
  const [scrollY, setScrollY] = useState(window.scrollY);
  const handleScrollY = () => {
    setScrollY(window.scrollY);
  };
  useEffect(() => {
    window.addEventListener('scroll', handleScrollY);
    // 需要手动移除
    return () => {
      window.removeEventListener('scroll', handleScrollY);
    };
  }, []);

  useEffectEvent('scroll', handleScrollY);
  return 
; } export default App;

12. useInsertionEffect

调用 useInsertionEffect 在任何可能需要读取布局的效果触发之前插入样式:

// CSS-in-JS 库内部逻辑
let isInserted = new Set();
function useCSS(rule) {
  useInsertionEffect(() => {
    // 如前所述,不建议运行时注入