React钩子简介



如果您阅读Twitter,您很可能知道Hooks是React的新功能,但是您可能会问, 我们如何在实践中使用它们 ? 在本文中,我们将向您展示一些使用钩子的示例。
要理解的关键思想之一是, 钩子使您无需编写类即可使用状态和其他React功能

背后的动机


尽管面向组件的体系结构允许我们在应用程序中重用视图,但是开发人员面临的最大问题之一是如何在组件之间重用基于状态的逻辑 。 当我们拥有状态逻辑相似的组件时,没有很好的解决方案来重用组件,这有时可能导致构造函数和生命周期方法中的逻辑重复。
要解决此问题,通常使用:

  • 高阶组件
  • 渲染道具

但是,这两种模式都有缺点,可能会导致代码库的复杂性。

Hooks旨在解决所有这些问题,使您无需编写类就可以编写可以访问状态,上下文,生命周期方法,ref等的功能组件。

钩在阿尔法


在开始潜水之前,重要的是要提到Hooks API的开发尚未完成。

另外, 官方文档非常好,我们建议您也阅读它,因为它广泛描述了引入Hooks的动机。
UPD最初的文章(您正在阅读的翻译)是在该API进行Alpha测试时编写的,目前React Hooks正式可以使用。 您可以在文章底部或发行说明中找到对该发行版进行的不可逆更改(与alpha相比)。

钩子与类的关系


如果您熟悉React,了解Hooks的最好方法之一就是了解如何重现我们过去使用Hooks处理类的行为。

回想一下,编写组件类时,我们经常需要:

  • 管理状态
  • 使用生命周期方法,例如componentDidMount()和componentDidUpdate()
  • 访问上下文(静态contextType)


使用React Hooks,我们可以在功能组件中重现类似的行为:

  • 要使用组件的状态,请使用useState()钩子
  • 代替使用诸如componentDidMount()和componentDidUpdate()之类的生命周期方法,请使用useEffect()挂钩。
  • 代替使用静态contextType属性,使用useContext()挂钩。

使用Hooks需要最新版本的React


您可以通过将package.json中的react和react-dom的值更改为“ next”,立即开始使用Hooks。



UseState()挂钩示例


状态是React不可或缺的一部分。 它允许我们声明包含数据的变量,这些变量又将在我们的应用程序中使用。 使用类,状态通常定义如下:



在Hooks之前,状态通常仅在组件-类中使用,但是如上所述, Hooks允许我们也将状态添加到功能组件中
让我们看下面的例子。 在这里,我们构建了一个背光开关,该开关根据状态值改变颜色。 为此,我们将使用钩子useState()。
这是完整的代码(和一个可执行的示例)-我们将在下面查看发生的情况。 通过单击图像,您可以在CodeSandBox上查看此示例。



我们的组件是一个功能


在上面的代码块中,我们首先从React导入useState 。 UseState是利用this.state可能提供的可能性的一种新方法。
然后注意,该组件是一个函数,而不是一个class 。 有趣!

读写状态


在此函数内部,我们调用useState创建状态为变量的变量:



useState用于声明状态变量,并可以使用任何类型的值进行初始化(与类中的状态不同,类应该是一个对象)。

如上所示,我们正在对useState的返回值使用解构。
  • 在这种情况下,light的第一个值是当前状态(例如this.state)
  • 第二个值是用于更新状态值(第一个值)的函数(如this.setState)。

然后,我们创建两个函数,每个函数将状态设置为0或1的不同值。



然后,我们将它们作为事件处理程序应用于视图中的按钮:



反应跟踪状态


当按下“ On”按钮时,将调用setOn函数,该函数将调用setLight(1)。 调用setLight(1) 更新下一个渲染的光照值 。 这似乎有些神奇,但是React会跟踪此变量的值,并在重新渲染此组件时传递新值。
然后,我们使用当前状态( )来确定灯是否应该点亮。 也就是说,我们根据light的值设置SVG的填充颜色。 如果light为0(关闭),则fillColor设置为#000000(如果为1(on),fillColor设置为#ffbb73)。

多种状态


尽管在上面的示例中我们没有这样做,但是您可以通过多次调用useState来创建多个状态。 例如:



注意
您应该了解使用挂钩的一些限制。 最重要的是,只应在函数的顶层调用钩子。 有关更多信息,请参见“ 挂钩规则 ”。


UseEffect()钩子示例


UseEffect Hook允许您在功能组件中执行副作用 。 副作用可能包括访问API,更新DOM,订阅事件处理程序-您所需要做的只是使“命令性”动作发生。

使用useEffect()挂钩,React知道您要在渲染后执行特定操作。

让我们看下面的例子。 我们将使用useEffect()调用API并获得响应。



此代码示例同时使用useStateuseEffect ,这是因为我们想将API调用的结果写入state。



接收数据并更新状态


要“使用效果”,我们需要将操作放入useEffect函数中,即,将“ action”效果作为匿名函数传递,作为useEffect的第一个参数。
在上面的示例中,我们指的是返回名称列表的API。 当响应返回时,我们将其转换为JSON,然后使用setNames(数据)设置状态。



使用效果时的性能问题



但是,关于使用useEffect还有其他要说的话

首先要考虑的是,默认情况下,我们的useEffect将在每个渲染器上调用! 好消息是我们不必担心过时的数据,但坏消息是我们可能不想为每个呈现都发出HTTP请求(在这种情况下)。

在这种情况下,您可以使用第二个useEffect参数跳过效果。 useEffect的第二个参数是我们要“观察”的变量列表,然后仅当这些值之一发生更改时,我们才会重新运行效果。

在上面的代码示例中,请注意,我们将传递一个空数组作为第二个参数。 告诉React的是,我们只想在安装组件时命名此效果。

要了解有关效果表现的更多信息, 请查阅白皮书中的本节。

此外,与useState函数一样,useEffect允许您使用多个实例,这意味着您可以具有多个useEffect函数。

UseContext()钩子示例


上下文点

React中的上下文是子组件访问父组件中的值的一种方式。

要了解上下文的需求:创建React应用程序时,您通常需要从React树的顶部向下传递值。 通过不使用上下文,您可以使道具通过不需要了解道具的组件。

将道具传递到“无关”组件的树上被亲切地称为“道具钻探”。
React Context通过允许您通过组件树与请求这些值的任何组件共享值来解决道具钻探问题。

useContext()简化了上下文的使用

使用useContext Hook,使用上下文比以往任何时候都容易。

useContext()函数采用一个上下文对象,该对象最初是从React.createContext()返回的,然后返回当前上下文的值。 让我们看下面的例子。



在上面的代码中,JediContext的上下文是使用React.createContext()创建的。

我们在我们的App组件中使用JediContext.Provider,并将该值设置为“ Luke”。 这意味着任何需要访问上下文的组件现在都可以读取该值。

要在Display()函数中读取此值,我们调用useContext并传递JediContext参数。

然后,我们传递从React.createContext获得的上下文对象,它会自动显示该值。 提供者值更新时,此挂钩将自动使用最后一个上下文值。

在大型应用程序中获取指向上下文的链接


上面,我们在两个组件中都创建了JediContext,但是在更大的应用程序中,Display和App将位于不同的文件中。 因此,如果您遇到类似的情况,您可能想知道:“我们如何获得文件之间的JediContext链接?”

答案是,您需要创建一个导出JediContext的新文件
例如,您可能有一个context.js文件,其中包含以下内容:



然后在App.js(和Display.js)中,您应该编写:



谢谢, 戴夫

UseRef()挂钩示例


Refs提供了一种访问在render()方法中创建的React元素的方法。
如果您是React ref的新手,可以阅读React ref的简介
useRef()函数返回一个ref对象。



useRef()和带有输入的表格


让我们来看一个使用useRef()挂钩的示例。



在上面的示例中,我们结合使用useRef()和useState()将输入值呈现给p标签。

在nameRef变量中创建Ref。 然后,变量nameRef可以在输入中使用,以ref形式给出。 本质上,这意味着现在可以通过ref访问输入字段的内容。

代码中的Submit按钮具有一个名为SubmitButton的onClick事件处理程序。 SubmitButton函数调用setName(通过useState创建)。

正如我们已经使用hookState所做的那样,setName将用于设置状态名称。 为了从输入标签中提取名称,我们读取了nameRef.current.value的值。

关于useRef的另一个注意事项是,它可以比ref属性更多地使用。

使用自定义挂钩


Hook的最酷的功能之一是,您可以通过创建自己的Hook轻松地在多个组件之间共享逻辑。

在下面的示例中,我们将创建一个自定义setCounter()挂钩,该挂钩可让我们跟踪状态并提供自定义状态更新功能!

另请参见,来自use-use的 useCounter Hook和来自Kent的useCounter




在上面的代码块中,我们创建了一个useCounter函数,用于存储钩子的逻辑。

请注意,useCounter可能会使用其他挂钩! 让我们首先通过useState创建一个新的Hook状态。

然后,我们定义两个辅助函数: increasdecrement ,它们调用setCount并相应地调整当前计数

最后,我们返回与钩子交互所需的链接。

问:会发生什么,返回带有对象的数组?
答:嗯,就像Hooks中的大多数事情一样,API约定尚未完成。 但是我们在这里返回的是一个数组,其中:

  • 第一项是挂钩的当前值。
  • 第二个元素是一个对象,其中包含用于与钩子交互的功能。

这个约定使您可以轻松地“重命名” Hook的当前值,就像我们上面对myCount所做的那样

但是,您可以从自定义挂钩中返回任何内容。

在上面的示例中,我们在视图中使用了增量减量作为onClick处理程序。 当用户按下按钮时,计数器将更新并在视图中重新显示(如myCount )。

为React Hook编写测试


为了编写钩子测试,我们将使用react-testing-library进行测试。

react-testing-library是用于测试React组件的非常轻量级的解决方案。 它是react-domreact-dom / test-utils的扩展。 使用react-testing-library可确保您的测试直接与DOM节点一起使用。

使用测试钩子,还不是很清楚。 当前,您不能单独测试挂钩。 相反,您需要将挂钩连接到组件并测试该组件。

因此,下面我们将为钩子编写测试,并与我们的组件交互,而不是直接与钩子交互。 好消息是我们的测试将看起来像常规的React测试。

测试useState()钩子


让我们看一个为useState Hook编写测试的示例 。 在上面的教程中,我们测试了上面使用的useState示例的更多变体。 我们将编写测试以确保按下“ Off”按钮将状态设置为0,并按下“ On”按钮将状态设置为1。



在上面的代码块中,我们首先从react-testing-library和被测组件中导入一些帮助器。

  • render ,这将有助于显示我们的组件。 将其渲染到添加到document.body的容器中
  • getByTestId 通过data-testid获取DOM元素
  • fireEvent ,用于“触发” DOM事件。 例如,它通过事件委托将事件处理程序附加到文档中并处理一些DOM事件。 通过单击一个按钮。

此外,在测试批准功能中,我们设置了具有data-testid的元素的变量值以及我们要在测试中使用的变量值。 通过链接到DOM上的元素,我们可以使用fireEvent方法模拟对按钮的单击。

该测试验证是否单击onButton ,状态值设置为1,并且当您单击offButton时状态为1。

测试useEffect()的钩子


在此示例中,我们将编写测试以使用useEffect Hook将产品添加到购物车。 项目数也存储在localStorage中。 下面的CodeSandbox中的index.js文件包含用于向购物车添加商品的实际逻辑。

我们将编写测试以确保购物车项目数的更新也反映在localStorage中,即使重新加载页面,购物车项目数也保持不变。



在确认测试的功能中,我们首先将localStorage中的 cartItem设置为0,这意味着购物车项目数为0。然后我们通过构造容器App组件重新渲染Rerender允许我们模拟页面重新加载。

然后,我们获得了指向按钮和p标记的链接,这些标记显示了当前购物篮值并将其设置为变量。

完成此操作后,测试将模拟对addButton的单击并检查当前购物篮计数器是否为1并重新加载页面,然后检查是否将localStoragecartItem设置为1。然后模拟对resetButton和检查当前购物车项目数是否设置为0。

测试useRef()的钩子


在此示例中,我们将测试useRef Hook,并将上面的原始useRef示例用作测试的基础。 UseRef用于从输入字段获取值,然后将值设置为state。 下面的CodeSandbox中的index.js文件包含用于输入值并将其发送的逻辑。



在批准测试的功能中,我们在输入字段中设置变量,显示ref的当前值的p标签和一个提交按钮。 我们还设置了要在newName变量的输入字段中输入的值。 这将用于测试中的验证。



fireEvent.change方法用于在输入字段中输入值,在这种情况下,将使用存储在常量newName中的名称,然后按下提交按钮。

然后,测试将检查按下按钮后的ref值是否与newName的匹配

最后,您应该看到“没有测试崩溃,祝贺您!” 控制台中的消息。

社区对钩的回应


自从引入React Hooks以来,社区对此功能感到非常满意,并且我们已经看到了许多使用React Hooks的示例。 这里是一些主要的:

  • 站点包含React Hooks的集合。
  • react-use ,一堆React Hooks附带的库。
  • CodeSandbox示例演示如何使用useEffect Hook通过react-spring创建动画
  • 一个 useMutableReducer 示例 ,它允许您简单地更改状态以在化简器中对其进行更新。
  • 示例在CodeSandbox上,该示例显示了父子通信的复杂集成用法和重发器的用法。
  • React Hooks 开关组件
  • React Hooks的另一个集合 ,具有用于输入值,设备方向和文档可见性的钩子。


不同类型的钩子


您可以在React应用程序中开始使用各种钩子。 它们在下面列出:

  • useState-允许我们编写具有访问状态的纯函数。
  • useEffect-允许我们执行副作用。 副作用可能是API调用,更新DOM,订阅事件处理程序。
  • useContext-允许您编写包含上下文的纯函数。
  • useReducer-为我们提供了一个类似于Redux的reducer的链接
  • useRef-允许您编写返回可变引用对象的纯函数。
  • useMemo - .
  • useCallback — Hook .
  • useImperativeMethods - , ref.
  • useMutationEffects — useEffect Hook , DOM-.
  • useLayoutEffect - DOM -.
  • hooks - .


hooks


Hooks的伟大之处在于它们与现有代码并存,因此您可以缓慢地进行更改以实现Hooks。您所要做的就是将React升级到支持钩子的版本。

但是,Hooks仍是一个实验性功能,React团队反复警告该API可能会发生变化。考虑到您被警告了。
钩子的出现对班级意味着什么?正如React团队所报告的那样,这些类仍然存在,它们是React代码库的重要组成部分,并且很可能会持续一段时间。

. Facebook , , , , . React Hooks, —

API- Hooks , Hooks, , .

其他资源


  • React React Hooks,
  • API .
  • RFC, ,


UPD
:
React 16.8 Hooks API. -:
— useMutationEffect.
— useImperativeMethods useImperativeHandle.
— useState useReducer Hooks.
— , useEffect/useMemo/useCallback Hooks.
— Object.is useState useReducer.
— Strict Mode (DEV-only).
— lazy initialization API useReducer Hook.
.

Source: https://habr.com/ru/post/zh-CN429712/


All Articles