流行的 React Hook 库

2025-06-08

流行的 React Hook 库

作者:拉斐尔·乌古✏️

React 生态系统的历程真是妙趣横生。自从React 16.3 中引入时间切片和 Suspense等功能以来,优秀的 React 团队也带来了一系列有趣的概念,但其中最引人注目的莫过于 React Hooks,它在React 16.8中首次稳定发布。

Hooks 提供了一种更简洁的代码编写方式,同时又无需担心向后兼容性问题,这意味着它将会继续存在下去。在这篇博文中,我将阐述 Hooks 如何成为救星。我将举例说明几个使用案例,这些案例将涵盖一些流行的 React Hook 库——包括主流库和自定义库(由像你我这样的爱好者创建)。让我们开始吧。

LogRocket 免费试用横幅

什么是 React Hooks?

简单来说,Hooks 提供了一种无需创建类组件即可传递状态和属性的媒介。采用基于函数的方法,使用 Hooks,我们可以将逻辑与 UI 分离,以便在应用程序的其他部分重用。请看下面的两个代码示例:

import React, { Component } from "react";
class MovieButton extends Component {
  constructor() {
    super();
    this.state = { buttonText: "Click to purchase movie tickets" };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState(() => {
      return { buttonText: "Enjoy your movie!" };
    });
  }
  render() {
    const { buttonText } = this.state;
    return <button onClick={this.handleClick}>{buttonText}</button>;
  }
}
export default MovieButton
Enter fullscreen mode Exit fullscreen mode

上面的要点展示了当按钮被点击时,内部状态MovieButton是如何改变的setState。使用 Hooks,我们可以描述这种内部状态的变化,而无需依赖类、构造函数或 setState:

import React, { useState } from "react";
export default function MovieButton() {
  const [buttonText, setButtonText] = useState("Click to purchase movie tickets");
  function handleClick() {
    return setButtonText("Enjoy your movie!");
  }
  return <button onClick={handleClick}>{buttonText}</button>;
}
Enter fullscreen mode Exit fullscreen mode

我选择先展示它,useState是因为它是React 生态系统中引入的第一个钩子useState它用于管理组件的本地状态,并在重新渲染之间保存它。令人着迷的是,该组件不必是 ES6 类组件——一个基本的 JavaScript 函数就可以,而且我们完成同样的事情,同时将代码库减少了十行。useState通过包含一对变量来实现——一个表示组件的实际初始状态,另一个表示您希望组件状态更新到的状态。

主流 React Hook 库

状态和数据获取

假设我想创建一个只使用 Hooks 的应用程序。很可能我需要在某个时候获取数据。一个好的方法是从在需要定义状态的地方定义状态开始。我将首先创建一个组件,并从 API 获取数据以供该组件渲染:

import React, { useState, useEffect } from "react";

const URL = "https://api.punkapi.com/v2/beers";
export default function Landing() {
  const [beer, setBeer] = useState([]);
  useEffect(() => {
    fetch(URL)
      .then(response => response.json())
      .then(beer => setBeer(beer));
  });
}
Enter fullscreen mode Exit fullscreen mode

这引出了useEffectHook。HookuseEffect允许你直接在函数组件内部处理生命周期事件。诸如设置订阅和数据获取之类的活动,我们原本需要使用生命周期方法来componentDidMount()完成,现在可以通过 来处理useEffect。根据React 文档

useEffect 与 React 类生命周期方法中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,但统一为单个 API

因此,在上面的例子中,我没有使用类组件,而是创建了一个函数并调用了fetch其中的方法useEffect。这里也不需要使用this.setState来更新状态setBeer,因为我创建了一个从 Hook 中提取的随机函数useState

如果您一直关注这一点并尝试使用上面的代码示例运行应用程序,那么您应该会遇到一个非常丑陋的无限循环:

为什么?useEffect的作用与componentDidMountcomponentDidUpdate和相同componentWillUnmount。因为每次获取数据后setBeer()都会更新 的状态beer,所以组件会更新并useEffect继续再次获取数据。

为了避免这个错误,我们需要通过提供一个空数组作为第二个参数来指定我们只想在组件安装时获取数据useEffect

import React, { useState, useEffect } from "react";

const URL = "https://api.punkapi.com/v2/beers";
export default function Landing() {
  const [beer, setBeer] = useState([]);
  useEffect(() => {
    fetch(URL)
      .then(response => response.json())
      .then(beer => setBeer(beer));
  }, {});
}
Enter fullscreen mode Exit fullscreen mode

表单处理

通过自定义 Hook(目前生态系统中已有海量 Hook),React 允许你重用和共享一些逻辑片段。根据经验,当组件中有大量逻辑时,这表明你应该重构它并分配部分逻辑,以避免组件臃肿。让我们依靠自定义 Hook 来创建与应用的某种交互——比如一个用户可以提交数据的表单。react -hook-form是一个完全使用 Hook 构建的库,提供表单验证功能。我们将像安装 npm 包一样将它添加到我们的应用程序中:

npm i react-hook-form
Enter fullscreen mode Exit fullscreen mode

然后导入我们需要的自定义 Hook – useForm

import React from "react";
import useForm from "react-hook-form";

const active = {
  fontSize: "15px"
};
export default function Purchase() {
  const { register, handleSubmit, errors } = useForm();
  const onSubmit = data => {  // upload the data retreived from the form to a database, return value to a user, etc
    console.log(data);
  };

  return (
    <div>
      <form onSubmit={handleSubmit(onSubmit)}>
        <label>Full Name</label>
        <input name="fullname" ref={register} />
        <label>Beer Name</label>
        <input
          name="beerName"
          ref={register({ required: true, maxLength: 10 })}
        />

        <select style={active} name="Title" ref={register({ required: true })}>
          <option value="">Select...</option>
          <option value="six-pack">Six Pack</option>
          <option value="twelve-pack">Twelve Pack</option>
        </select>
        <label>
          <input type="checkbox" placeholder="+18" name="+18" ref={register} />I
          am 18 and above
        </label>
        {errors.beerType && <p>This field is required</p>}
        <input type="submit" value="Pay Here" />
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

工作原理概述:

路由

应用程序正在逐渐扩展,此时,如果能包含每个包含多个组件的应用程序所需的路由就更好了。我们将使用hooksrouter一个很棒的库,它可以导出自定义钩子useRoutes

npm i hookrouter
Enter fullscreen mode Exit fullscreen mode

useRoutes评估预定义的路由对象并在路由匹配时返回结果:

import React from "react";
import Purchase from "./components/Purchase";
import Landing from "./components/Landing";
import HomePage from "./components/HomePage";
const Routes = {
  "/": () => ,
  "/purchase": () => ,
  "/landing": () => 
};

export default Routes;
Enter fullscreen mode Exit fullscreen mode

这减少了使用传统 React Router 时需要编写的繁琐代码,因为我们需要<Route/>为应用中的所有路由渲染组件,并在其中传递 props。现在,我们要做的就是导入Routes组件并将其传递给useRoutesHook:

// index.js or where you choose to render your entire app from
import { useRoutes } from "hookrouter";
import Routes from "./router";

function App() {
  const routeResult = useRoutes(Routes);
  return <div>{routeResult}</div>;
}
Enter fullscreen mode Exit fullscreen mode

让我们看看浏览应用程序的感觉如何:

处理复杂的状态管理

当然,useState它用于管理状态,但如果您的应用变得越来越复杂,并且必须在一个状态对象中处理多个状态转换,该怎么办?这时,useReducerHook 就派上用场了。useReducer当您需要处理多个对象或数组中的数据,并且还要确保这些数据易于维护和预测时,Hook 是首选。为了更好地描述useReducerHook,我将在应用中添加一个包含多状态架构的页面——比如一个用户可以创建自己的啤酒配方的地方:

import React, { useReducer } from "react";

const myStyle = {
  color: "white",
  fontSize: "20px"
};

export default function Recipe() {
  const initialState = {
    RecipePrice: 0,
    recipe: {
      price: 100,
      name: "Oompa Loompa",
      image:
        "https://res.cloudinary.com/fullstackmafia/image/upload/v1568016744/20110111-132155-Homebrew-Grain_uihhas.jpg",
      ingredients: []
    },
    stockpile: [
      { id: "1", name: "Extra Pale Malt", price: 10 },
      { id: "2", name: "Ahtanum Hops", price: 6 },
      { id: "3", name: "Wyeast 1056", price: 8 },
      { id: "4", name: "Chinook", price: 5 }
    ]
  };
  const reducer = (state, action) => {
    switch (action.type) {
      case "REMOVE_ITEM":
        return {
          ...state,
          RecipePrice: state.RecipePrice - action.item.price,
          recipe: {
            ...state.recipe,
            ingredients: state.recipe.ingredients.filter(
              y => y.id !== action.item.id
            )
          },
          stockpile: [...state.stockpile, action.item]
        };
      case "ADD_ITEM":
        return {
          ...state,
          RecipePrice: state.RecipePrice + action.item.price,
          recipe: {
            ...state.recipe,
            ingredients: [...state.recipe.ingredients, action.item]
          },
          stockpile: state.stockpile.filter(x => x.id !== action.item.id)
        };
      default:
        return state;
    }
  };

  const [state, dispatch] = useReducer(reducer, initialState);

  const removeFeature = item => {
    dispatch({ type: "REMOVE_ITEM", item });
  };

  const addItem = item => {
    dispatch({ type: "ADD_ITEM", item });
  };

  return (
    <div className="boxes" style={myStyle}>
      <div className="box">
    <h4>Ingredients Stockpile</h4>
        <figure>
          <img width={"300px"} src={state.recipe.image} alt="my recipe" />
        </figure>
        <h2>{state.recipe.name}</h2>
        <pre>Amount: ${state.recipe.price}</pre>
        <div className="content">
          <h5>Added ingredients:</h5>
          {state.recipe.ingredients.length ? (
            <ol type="1">
              {state.recipe.ingredients.map(item => (
                <li key={item.id}>
                  <button
                    onClick={() => removeFeature(item)}
                    className="button"
                  >
                    REMOVE FROM LIST
                  </button>
                  {item.name}
                </li>
              ))}
            </ol>
          ) : (
            <pre>You can purchase items from the stockpile.</pre>
          )}
        </div>
      </div>
      <div className="box">
        <div className="content">
          {state.stockpile.length ? (
            <ol type="1">
              {state.stockpile.map(item => (
                <li key={item.id}>
                  <button onClick={() => addItem(item)} className="button">
                    ADD TO LIST
                  </button>
                  {item.name} (+{item.price})
                </li>
              ))}
            </ol>
          ) : (
            <pre>Nice looking recipe!</pre>
          )}
        </div>

        <div className="content">
          <h4>Total Amount: ${state.recipe.price + state.RecipePrice}</h4>
        </div>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

如果您熟悉 Redux,您会line 54在上面的代码示例中认出它useReducer接受一个包含组件初始状态的 Reducer 和一个 Action——通常是一个用于根据需要更新组件状态的 dispatch 方法。因此,使用 Reducer,我们可以将多个状态合并为一个,而不必创建多个单个状态 Hook。让我们看看这个组件是如何工作的:

钩子收藏

自 Hooks 发布以来,React 社区的热情令人惊叹。大量的自定义 Hooks 应运而生,它们展现了令人惊叹的功能。以下是一些你绝对应该了解的自定义 React Hook 合集:

React Hooks 集合包含 300 多个自定义 Hook,其中最受欢迎的是useArray提供多种数组操作方法的 Hook,这是开发人员的日常工作。让我们更新我们的应用程序以包含这个useArrayHook:

import React from "react";
import { useArray } from "react-hanger";

const myStyle = {
  color: "white"
};
export default function App() {
  const todos = useArray(["35cl", "50cl", "60cl"]);
  return (
    <div style={myStyle}>
      <h3>Measures</h3>
      <button
        onClick={() =>
          todos.add(Math.floor(Math.random() * (60 - 35 + 1)) + 35 + "cl")
        }
      >
        CUSTOM
      </button>

      <ul>
        {todos.value.map((todo, i) => (
          <div>
            <li key={i}>{todo}</li>
            <button onClick={() => todos.removeIndex(i)}>
              Remove from list
            </button>
          </div>
        ))}
      </ul>
      <button onClick={todos.clear}>clear</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

让我们看看它是如何工作的:

另一个我非常感兴趣的集合是useHooks,它包含useLockBodyScroll一个 Hook ,可以防止用户在特定组件上滚动。我发现这个 Hook 可以与 React 的内置useLayoutEffectHook 配合使用——后者从 DOM 读取布局并同步重新渲染。要实现useLockBodyScroll,首先需要将其定义为一个函数:

import { useLayoutEffect } from "react";

export default function useLockBodyScroll() {
  useLayoutEffect(() => {
    // Get original value of body overflow
    const originalStyle = window.getComputedStyle(document.body).overflow;
    // Prevent scrolling on mount
    document.body.style.overflow = "hidden";
    // Re-enable scrolling when component unmounts
    return () => (document.body.style.overflow = originalStyle);
  }, []); // Empty array ensures effect is only run on mount and unmount
}
Enter fullscreen mode Exit fullscreen mode

然后在所需的组件中导入它:

import useLockBodyScroll from "./useLockBodyScroll";

export default function Landing() {
    useLockBodyScroll();
    const [data, setData] = useState([]);
    useEffect(() => {
        fetch(URL)
            .then(response => response.json())
            .then(data => setData(data));
    }, []);
    return ( <
        div >
        <
        button >
        <
        A style = {
            {
                textDecoration: "none"
            }
        }
        href = "/" >
        HOME <
        /A>{" "} <
        br / >
        <
        /button> {
            data.map(item => ( <
                Item.Group key = {
                    item.id
                }
                style = {
                    divStyle
                } >
                <
                Item >
                <
                Item.Image width = "80"
                size = "tiny"
                src = {
                    item.image_url
                }
                alt = "Beer Flask" /
                >
                <
                Item.Content >
                <
                Item.Header > {
                    item.name
                } < /Item.Header> <
                Item.Extra > {
                    item.tagline
                } < /Item.Extra> <
                Item.Meta style = {
                    {
                        lineHeight: 1.5
                    }
                } > {
                    item.description
                } <
                /Item.Meta> <
                /Item.Content> <
                /Item> <
                /Item.Group>
            ))
        } <
        /div>
    );
}
Enter fullscreen mode Exit fullscreen mode

让我们看看它是如何工作的。我们的浏览器中应该没有滚动条:

好了,我们的应用暂时完成了。我是不是忘了什么你觉得非常重要的事情?欢迎你在 CodeSandbox 中改进这个 demo。

概括

我认为 Hooks 是 React 长期以来最伟大的成就。尽管目前已经取得了很多成就,但我们仍有许多工作要做。在 React 爱好者中,某些论坛上存在这样一种争论:React 提供创建自定义 Hooks 的功能会导致生态系统中 Hooks 过载——就像 jQuery 插件那样。你对 Hooks 有什么看法?你最近发现了哪些很棒的 Hooks?请在下方评论区留言告诉我。谢谢!


编者注:觉得这篇文章有什么问题?您可以在这里找到正确版本

插件:LogRocket,一个用于 Web 应用的 DVR

 
LogRocket 仪表板免费试用横幅
 
LogRocket是一款前端日志工具,可让您重播问题,就像它们发生在您自己的浏览器中一样。无需猜测错误发生的原因,也无需要求用户提供屏幕截图和日志转储,LogRocket 让您重播会话,快速了解问题所在。它可与任何应用程序完美兼容,不受框架限制,并且提供插件来记录来自 Redux、Vuex 和 @ngrx/store 的额外上下文。
 
除了记录 Redux 操作和状态外,LogRocket 还记录控制台日志、JavaScript 错误、堆栈跟踪、带有标头 + 正文的网络请求/响应、浏览器元数据以及自定义日志。它还会对 DOM 进行插桩,以记录页面上的 HTML 和 CSS,即使是最复杂的单页应用程序,也能重现像素完美的视频。
 
免费试用


流行的 React Hook 库一文首先出现在LogRocket 博客上。

鏂囩珷鏉ユ簮锛�https://dev.to/bnevilleoneill/popular-react-hook-libraries-1ih3
PREV
解决 React Hooks 问题的解决方案
NEXT
如何使用 SASS 编写可重用的 CSS