React Hooks API 入门

已发表: 2022-03-10
快速总结 ↬在本教程中,您将学习和理解什么是 React Hooks、可用的基本 React Hooks 以及如何为您的 React 应用程序编写它们的示例。 在此过程中,您还将了解 React 16.8 附带的一些附加钩子,以及如何编写自己的自定义 React Hooks。

当 React 16.8 于 2019 年 2 月初正式发布时,它附带了一个额外的 API,让您无需编写类即可在 React 中使用状态和其他功能。 这个额外的 API 称为Hooks ,它们在 React 生态系统中越来越流行,从开源项目到用于生产应用程序。

React Hooks 是完全可选的,这意味着不需要重写现有代码,它们不包含任何重大更改,并且可以与 React 16.8 一起使用。 一些好奇的开发者甚至在 Hooks API 正式发布之前就已经在使用它,但当时它并不稳定,只是一个实验性功能。 现在它已经稳定并推荐给 React 开发者使用。

注意我们一般不会谈论 React 或 JavaScript。 在学习本教程时,对 ReactJS 和 JavaScript 的良好了解将派上用场。

什么是 React Hooks?

React Hooks 是内置函数,允许 React 开发人员在功能组件中使用状态和生命周期方法,它们还可以与现有代码一起工作,因此可以很容易地被采用到代码库中。 Hooks 向公众推销的方式是它们允许开发人员在功能组件中使用状态,但在底层,Hooks 比这更强大。 它们允许 React 开发人员享受以下好处:

  • 改进的代码重用;
  • 更好的代码组合;
  • 更好的默认值;
  • 使用自定义挂钩共享非可视化逻辑;
  • 灵活地上下移动components树。

使用 React Hooks,开发人员可以使用功能组件来完成他们需要做的几乎所有事情,从渲染 UI 到处理状态和逻辑——这非常简洁。

React Hooks 发布背后的动机

根据 ReactJS 官方文档,React Hooks 发布的动机如下:

  • 在组件之间重用有状态逻辑是很困难的。
    使用 Hooks,您可以在组件之间重用逻辑,而无需更改其架构或结构。
  • 复杂的组件可能难以理解。
    当组件变得更大并执行许多操作时,从长远来看,它变得难以理解。 Hooks 解决了这个问题,它允许您根据这个分离的组件的相关部分(例如设置订阅或获取数据)将特定的单个组件分成各种较小的函数,而不是必须根据生命周期方法强制拆分。
  • 课程相当混乱。
    类是正确学习 React 的障碍; 你需要了解this在 JavaScript 中是如何工作的,这与其他语言不同。 React Hooks 通过允许开发人员使用 React 的最佳功能而无需使用类来解决这个问题。
跳跃后更多! 继续往下看↓

钩子规则

正如 React 核心团队在钩子提案文档中概述的那样,有两个主要规则必须严格遵守。

  • 确保不要在循环、条件或嵌套函数中使用 Hook;
  • 仅在 React 函数内部使用 Hooks。

基本的反应钩子

React 16.8 附带了 10 个内置钩子,但基本(常用)钩子包括:

  • useState()
  • useEffect()
  • useContext()
  • useReducer()

这些是 React 开发人员常用的 4 个基本钩子,它们已将 React Hooks 应用到他们的代码库中。

useState()

useState()钩子允许 React 开发人员更新、处理和操作功能组件内部的状态,而无需将其转换为类组件。 让我们使用下面的代码片段是一个简单的年龄计数器组件,我们将使用它来解释useState()挂钩的功能和语法。

 function App() { const [age, setAge] = useState(19); const handleClick = () => setAge(age + 1) return <div> I am {age} Years Old <div> <button onClick={handleClick}>Increase my age! </button> </div> </div> }

如果您已经注意到,我们的组件看起来非常简单、简洁,现在它是一个函数式组件,并且没有类组件所具有的复杂程度。

useState()钩子接收初始状态作为参数,然后返回,通过使用 JavaScript 中的数组解构,可以将数组中的两个变量命名为什么。 第一个变量是实际状态,而第二个变量是一个函数,用于通过提供新状态来更新状态。

我们完成的 React 应用程序(大预览)

这就是我们的组件在 React 应用程序中呈现时的外观。 通过单击“增加我的年龄”按钮,年龄的状态将发生变化,并且组件将像具有状态的类组件一样工作。

useEffect()

useEffect()钩子接受一个包含有效代码的函数。 在函数式组件中,不允许将诸如突变、订阅、计时器、日志记录等效果放置在函数式组件中,因为这样做会导致 UI 渲染时出现很多不一致,并且还会出现令人困惑的错误。

在使用useEffect()钩子时,传递给它的效果函数将在渲染显示在屏幕上后立即执行。 效果基本上是窥探构建 UI 的命令式方式,这与 React 的函数式方式完全不同。

默认情况下,效果主要在渲染完成后执行,但您也可以选择在某些值更改时触发它们。

useEffect()钩子主要用于副作用,这些副作用通常用于与浏览器/DOM API 或类似外部 API 的数据获取或订阅的交互。 此外,如果您已经熟悉 React 生命周期方法的工作原理,您还可以将useEffect()钩子视为组件安装更新卸载——所有这些都结合在一个函数中。 它让我们可以在功能组件中复制生命周期方法。

我们将使用下面的代码片段来解释使用useEffect()挂钩的最基本方法。

第 1 步:定义应用程序的状态

import React, {useState} from 'react'; function App() { //Define State const [name, setName] = useState({firstName: 'name', surname: 'surname'}); const [title, setTitle] = useState('BIO'); return( <div> <h1>Title: {title}</h1> <h3>Name: {name.firstName}</h3> <h3>Surname: {name.surname}</h3> </div> ); }; export default App

就像我们在上一节中讨论的如何使用useState()钩子来处理功能组件内部的状态一样,我们在代码片段中使用它来设置呈现我全名的应用程序的状态。

第 2 步:调用 useEffect Hook

 import React, {useState, useEffect} from 'react'; function App() { //Define State const [name, setName] = useState({firstName: 'name', surname: 'surname'}); const [title, setTitle] = useState('BIO'); //Call the use effect hook useEffect(() => { setName({FirstName: 'Shedrack', surname: 'Akintayo'}) }, [])//pass in an empty array as a second argument return( <div> <h1>Title: {title}</h1> <h3>Name: {name.firstName}</h3> <h3>Surname: {name.surname}</h3> </div> ); }; export default App

我们现在已经导入了useEffect钩子,并且还使用了useEffect()函数来设置我们的 name 和 surname 属性的状态,这非常简洁。

您可能已经注意到第二个参数中的useEffect钩子,它是一个空数组; 这是因为它包含对setFullName的调用,该调用没有依赖项列表。 传递第二个参数将阻止无限的更新链( componentDidUpdate() ),它还将允许我们的useEffect()挂钩充当componentDidMount生命周期方法并渲染一次,而无需在树中的每次更改时重新渲染。

我们的 React 应用程序现在应该如下所示:

使用useEffect Hook 反应应用程序(大预览)

我们还可以通过调用setTitle()函数在useEffect()函数中更改应用程序的title属性,如下所示:

 import React, {useState, useEffect} from 'react'; function App() { //Define State const [name, setName] = useState({firstName: 'name', surname: 'surname'}); const [title, setTitle] = useState('BIO'); //Call the use effect hook useEffect(() => { setName({firstName: 'Shedrack', surname: 'Akintayo'}) setTitle({'My Full Name'}) //Set Title }, [])// pass in an empty array as a second argument return( <div> <h1>Title: {title}</h1> <h3>Name: {name.firstName}</h3> <h3>Surname: {name.surname}</h3> </div> ); }; export default App

现在,在我们的应用程序重新渲染后,它现在显示了新标题。

我们完成的项目(大预览)

useContext()

useContext()钩子接受一个上下文对象,即从React.createContext返回的值,然后它返回该上下文的当前上下文值。

这个钩子使功能组件可以轻松访问您的 React 应用程序上下文。 在引入useContext钩子之前,您需要设置一个contextType或一个<Consumer>来访问从类组件中的某个提供者传递下来的全局状态。

基本上, useContext钩子与 React Context API 一起使用,这是一种在整个应用程序中深度共享数据的方法,而无需手动将应用程序道具向下传递到各个级别。 现在, useContext()使使用 Context 变得更容易了。

下面的代码片段将展示 Context API 的工作原理以及useContext Hook 如何使其变得更好。

使用上下文 API 的常规方式

import React from "react"; import ReactDOM from "react-dom"; const NumberContext = React.createContext(); function App() { return ( <NumberContext.Provider value={45}> <div> <Display /> </div> </NumberContext.Provider> ); } function Display() { return ( <NumberContext.Consumer> {value => <div>The answer to the question is {value}.</div>} </NumberContext.Consumer> ); } ReactDOM.render(<App />, document.querySelector("#root"));

现在让我们分解代码片段并解释每个概念。

下面,我们正在创建一个名为NumberContext的上下文。 它旨在返回一个具有两个值的对象: { Provider, Consumer }

 const NumberContext = React.createContext();

然后我们使用从我们创建的NumberContext返回的Provider值来使所有子级都可以使用特定的值。

 function App() { return ( <NumberContext.Provider value={45}> <div> <Display /> </div> </NumberContext.Provider> ); }

有了这个,我们可以使用从我们创建的NumberContext返回的Consumer值来获取我们为所有孩子提供的值。 如果你注意到了,这个组件没有得到任何道具。

 function Display() { return ( <NumberContext.Consumer> {value => <div>The answer to the question is {value}.</div>} </NumberContext.Consumer> ); } ReactDOM.render(<App />, document.querySelector("#root"));

请注意,我们如何通过将我们的内容包装在NumberContext.Consumer中并使用 render props 方法来检索值并呈现它,从而将App组件中的值获取到Display组件中。

一切正常,我们使用的 render props 方法是处理动态数据的一个非常好的模式,但从长远来看,如果你不习惯它,它确实会引入一些不必要的嵌套和混乱。

使用 useContext 方法

为了解释useContext方法,我们将使用 useContext 钩子重写Display组件。

 // import useContext (or we could write React.useContext) import React, { useContext } from 'react'; // old code goes here function Display() { const value = useContext(NumberContext); return <div>The answer is {value}.</div>; }

这就是我们需要做的所有事情来展示我们的价值。 很整洁,对吧? 您调用useContext()钩子并传入我们创建的上下文对象,然后我们从中获取值。

注意:不要忘记传递给 useContext 挂钩的参数必须是上下文对象本身,并且任何调用 useContext 的组件都会在上下文值更改时重新渲染。

useReducer()

useReducer钩子用于处理复杂的状态和状态转换。 它接受一个reducer函数和一个初始状态输入; 然后,它通过数组解构返回当前状态以及一个dispatch函数作为输出。

下面的代码是使用useReducer钩子的正确语法。

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

它是useState钩子的一种替代方案; 当您有与多个子值有关的复杂状态逻辑或下一个状态依赖于前一个状态时,通常最好使用useState

其他可用的 React Hooks

useCallback 这个钩子返回一个被记忆的回调函数,只有当依赖树中的一个依赖改变时才会改变。
useMemo 这个钩子返回一个记忆值,你可以传入一个“create”函数和一个依赖数组。 如果依赖关系树中的依赖关系之一发生变化,它返回的值只会再次使用记忆值。
useRef 这个钩子返回一个可变的 ref 对象,其.current属性被初始化为传递的参数( initialValue )。 返回的对象将在组件的整个生命周期内可用。
useImperativeHandle 这个钩子用于自定义在 React 中使用 refs 时可用于父组件的实例值。
useLayoutEffect 这个钩子类似于useEffect钩子,但是,它在所有 DOM 突变后同步触发。 它也以与componentDidUpdatecomponentDidMount相同的方式呈现。
useDebugValue 这个钩子可用于在 React 开发工具中显示自定义钩子的标签。 它对于使用 React 开发工具进行调试非常有用。

自定义反应钩子

“自定义 Hook”是一个 JavaScript 函数,其名称以use为前缀,可用于调用其他 Hook。 它还允许您将组件逻辑提取到可重用的函数中; 它们是普通的 JavaScript 函数,可以利用其中的其他 Hook,并且还包含可以在多个组件中使用的通用有状态逻辑。

下面的代码片段演示了一个用于实现无限滚动的自定义 React Hook 示例(作者 Paulo Levy):

 import { useState } from "react"; export const useInfiniteScroll = (start = 30, pace = 10) => { const [limit, setLimit] = useState(start); window.onscroll = () => { if ( window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight ) { setLimit(limit + pace); } }; return limit; };

这个自定义 Hook 接受两个参数,即startpace 。 start 参数是要渲染的元素的起始数量,而 pace 参数是要渲染的后续元素的数量。 默认情况下, startpace参数分别设置为3010 ,这意味着您实际上可以在没有任何参数的情况下调用 Hook,并且将使用这些默认值。

因此,为了在 React 应用程序中使用这个 Hook,我们将它与返回“假”数据的在线 API 一起使用:

 import React, { useState, useEffect } from "react"; import { useInfiniteScroll } from "./useInfiniteScroll"; const App = () => { let infiniteScroll = useInfiniteScroll(); const [tableContent, setTableContent] = useState([]); useEffect(() => { fetch("https://jsonplaceholder.typicode.com/todos/") .then(response => response.json()) .then(json => setTableContent(json)); }, []); return ( <div style={{ textAlign: "center" }}> <table> <thead> <tr> <th>User ID</th> <th>Title</th> </tr> </thead> <tbody> {tableContent.slice(0, infiniteScroll).map(content => { return ( <tr key={content.id}> <td style={{ paddingTop: "10px" }}>{content.userId}</td> <td style={{ paddingTop: "10px" }}>{content.title}</td> </tr> ); })} </tbody> </table> </div> ); }; export default App;

上面的代码将呈现一个假数据列表( userIDtitle ),它们利用无限滚动挂钩在屏幕上显示初始数据数量。

结论

我希望你喜欢学习本教程。 你总是可以从下面的参考资料中阅读更多关于 React Hooks 的信息。

如果您有任何问题,您可以在评论部分留下它们,我很乐意为您一一解答!

本文的支持 repo 可在 Github 上找到。

资源和进一步阅读

  • “Hooks API 参考”,React.js 文档
  • “什么是 React Hooks?”,Robin Wieruch
  • useContext挂钩如何工作,”戴夫·塞迪亚
  • “React Hooks:如何使用useEffect() ”,Hossein Ahmadi,Medium
  • “编写你自己的自定义 React Hooks”,Aayush Jaiswal,Medium
  • “易于理解的 React Hook 食谱”,Gabe Ragland,useHooks()