React Hooks API 入门
已发表: 2022-03-10当 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 应用程序中呈现时的外观。 通过单击“增加我的年龄”按钮,年龄的状态将发生变化,并且组件将像具有状态的类组件一样工作。
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 突变后同步触发。 它也以与componentDidUpdate 和componentDidMount 相同的方式呈现。 |
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 接受两个参数,即start
和pace
。 start 参数是要渲染的元素的起始数量,而 pace 参数是要渲染的后续元素的数量。 默认情况下, start
和pace
参数分别设置为30
和10
,这意味着您实际上可以在没有任何参数的情况下调用 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;
上面的代码将呈现一个假数据列表( userID
和title
),它们利用无限滚动挂钩在屏幕上显示初始数据数量。
结论
我希望你喜欢学习本教程。 你总是可以从下面的参考资料中阅读更多关于 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()