如何从类组件到React Hooks

407 阅读9分钟

本教程包括:

  1. 如何在Hooks和类组件中处理状态
  2. 了解什么是自定义Hooks
  3. 为React Hooks编写和进行自动化测试

从16.8版本开始,React提供了一种使用组件和全局状态的方法,而不需要类组件。但这并不意味着Hooks是类组件的替代品。使用类组件有一些好处,我将在本教程的后面描述。首先,我将带领你了解如何在Hooks和类组件中处理状态,了解什么是自定义Hooks,最后为Hooks编写测试并将你的应用程序集成到CircleCI中。

前提条件

要跟上本教程,你将需要。

我们的教程是与平台无关的,但使用CircleCI作为一个例子。如果你没有CircleCI账户,请**在这里注册一个免费账户。**

钩子与基于类的组件

类组件基本上是JavaScript面向对象的类,带有你可以用来渲染React组件的功能。在React中使用类的好处是,它们包含生命周期方法,可以识别状态变化并使用关键字this.state ,更新全局状态或组件状态。相比之下,Hooks在React功能组件中使用,使你能够在功能组件中拥有组件状态和其他反应特性,而不需要类。React提供了一种方法来Hook到全局状态,而不需要类的生命周期方法来更新你的应用程序的全局和局部状态。

在本节中,你将制作一个计数器组件,利用React Hooks和一个类来增加和减少计数。然后我将向你展示如何在两者中初始化和更新状态。

你的第一步是克隆你将要使用的版本库。

克隆版本库

在你喜欢的工作目录的终端上,运行这些命令。

git clone https://github.com/mwaz/class-components-to-react-hooks.git # Clone repository

cd class-components-to-react-hooks # Change directory to the cloned repository

克隆完版本库后,安装依赖项并启动应用程序。运行这个命令。

npm install # Install dependencies

npm start # Start the application

一旦运行了start 命令,你应该能够看到应用程序在浏览器上执行,如下图所示。

Counter application

这是一个简单的React类组件,是由下面的片段创建的。

// src/class.js
import React from "react";

class CounterClass extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  increment = () => {
    this.setState({
      count: this.state.count + 1,
    });
  }
  decrement = () => {
    this.setState({
      count: Math.max(this.state.count - 1),
    });
  }
  render() {
    return (
      <div className="counter">
        <h1>COUNTER</h1>
        <div className="buttons">
          <button onClick={this.increment}>Increment</button>
          <button onClick={this.decrement}>Decrement</button>
        </div>
        <p>{this.state.count}</p>
      </div>
    );
  }
}

export default CounterClass;

这个代码段的构造方法将状态设置为this.state ,并将计数初始化为0 。然后它定义了当按钮被点击时被调用的函数。这些函数使用setState() 方法更新状态。这就是用类来更新计数器应用程序的类组件实现。接下来我们将回顾如何使用功能组件和Hooks来实现同样的内容。克隆库中的src/hooks.js 包含了Hook的实现。

// src/hooks.js
import { useState } from 'react'

export default function CounterHooks() {
const [ value, setValue]= useState(0);

const handleIncrement = () => {
    setValue(value + 1)
  }
  const handleDecrement = () => {
    setValue(value - 1)
  }
  return (
    <div className="counter">
      <h1>COUNTER</h1>
      <div className="buttons">
        <button data-testid="increment" onClick={handleIncrement}>Increment</button>
        <button data-testid="decrement" onClick={handleDecrement}>Decrement</button>
      </div>
      <p data-testid="count">{value}</p>
    </div>
  );
}

你可以使用useState() Hook来初始化状态,而不是像前面的片段中那样使用this.stateuseState Hook也有能力与应用程序中的其他组件共享状态,就像使用this.state 的类组件一样。

这些代码片段表明,代码的可读性得到了改善。不仅消除了复杂性,而且你还使功能组件只做了一件事--渲染计数器的应用。现在你知道什么是React Hooks了,为什么不探索一下React中最常见的Hooks以及它们的使用方法呢?

useState vs useEffect Hooks

在React中,你可以使用不同的Hooks来执行操作。其中之一是useEffect() Hook。这个Hook可以帮助你处理React领域之外的事情,如API调用、异步事件和其他副作用。一个简单的useEffect Hook的结构显示在这个片段中。

 useEffect(() => {
    //Your code here
  }, [])

useEffect Hook所期望的第一个参数是一个回调函数,你在那里写下要执行的代码。第二个参数是一个数组[] ,称为dependency array 。如果省略了这个数组,回调函数将在每次代码改变时运行。如果数组为空,回调函数将运行一次。如果提供了一个值,回调函数将在每次数值变化时运行。

**注意。**依赖数组是一个采取依赖关系或变量的数组,如果值发生变化,回调函数会再次运行。

接下来,尝试在简单的逻辑中使用useEffect() Hook,将count的值记录到Chrome浏览器的控制台。在这种情况下,你想返回count的值,所以你会将该值添加到依赖关系数组中,如本片段所示。

 useEffect(() => {
    console.log(value);
  }, [value])

当组件加载并调用useEffect Hook时,控制台会将计数的值记录到Chrome浏览器控制台。每当计数的值发生变化时,控制台就会记录下计数的新值。

相比之下,useState() 钩子是一个用于初始化应用程序状态的钩子。它接受一个值作为参数,并返回一个包含两个值的数组。第一个值是当前状态,第二个值是一个你可以用来更新状态的函数。

使用React Hooks,如useState()useEffect() ,你可以取消使用生命周期方法,如componentDidMount()componentDidUpdate() 。相反,你可以使用Hook来处理状态逻辑。

注意。 生命周期方法是内置于React的。它们被用来在某个动作发生时执行操作,比如渲染、挂载、更新和卸载。它们只在基于类的组件中使用。

在探索了一些Hooks之后,你可以继续探索使用Hooks的一些优点和缺点。

钩子的优点

  • this Hooks不需要为click 事件绑定函数,也不需要在组件或全局状态下访问values
  • 钩子使代码更简洁,易于阅读和测试。
  • 钩子提供了更多的灵活性,它们可以被重复使用,特别是在多个组件中的自定义钩子。
  • 使用Hooks,你不需要使用生命周期方法。副作用可以由一个单一的函数来处理。

钩子的劣势

  • 开始使用Hooks可能是一个挑战,特别是对于一个新的开发者。
  • 每次状态改变时,组件都会重新显示,除非你使用其他Hooks来防止这种情况。

创建自定义Hooks

在上一节中,我描述了使用Hooks的优点和缺点。在这一节中,我将引导你创建一个可以在计数器应用程序中任何地方使用的自定义Hook。在src/components/useCounter.js 文件中添加这个代码片段。

// src/components/useCounter.js
import { useState, useEffect } from "react";

export function useCounter() {
  const [value, setValue] = useState(0);
  const [isEven, setIsEven] = useState(false);

  useEffect(() => {
    if (value % 2 === 0) {
      setIsEven(true);
    } else {
      setIsEven(false);
    }
  }, [value]);

  const handleIncrement = () => {
    setValue(value + 1);
  };
  const handleDecrement = () => {
    setValue(value - 1);
  };

  return [value, isEven, handleIncrement, handleDecrement];
}

这个代码片断添加了一个新的状态值,isEven ,用来检查数值是否为偶数。该代码段继续检查计数值,并确定它是偶数还是奇数。它根据该值将isEven 设置为真或假。

useEffect Hook里面的回调函数使用一个if - else 语句来设置isEven 的值。它还使用依赖数组中的一个值来确保每次计数发生变化时,无论是递减还是递增,函数都会运行。

useCounter Hook返回状态值以及incrementdecrement 函数,这样你就可以在Hooks组件中访问它们。

现在你有了自定义Hook,你可以用它来设置和更新custom-hook.js 文件中的状态。

// src/components/custom-hook.js
import { useCounter } from './useCounter'

export default function CounterHooks() {
const [ value, isEven, handleIncrement, handleDecrement ]= useCounter();

 return (
    <div className="counter">
      <h1>COUNTER</h1>
      <div className="buttons">
        <button data-testid="increment"  onClick={handleIncrement}>Increment</button>
        <button data-testid="decrement" onClick={handleDecrement}>Decrement</button>
      </div>
      <p data-testid="count">{value}</p>
      <div className={isEven ? "even" : "odd"}>{ isEven ? "Even" : "Odd"}</div>
    </div>
  );
}

这个代码片段使用useCounter() Hook来设置状态值,同时访问incrementdecrement 函数。它使用这些函数来更新状态。isEven 状态值显示计数器是偶数还是奇数,取决于应用程序上显示的计数器数字。

Odd and Even hooks display

现在你已经对Hooks有了一些兴趣,是时候学习如何测试它们了。

测试Hooks

在本节中,我将描述如何为Hook组件编写测试。你将使用Jestreact-testing-library,这两个东西在你设置克隆的应用程序时已经安装了。

从测试按钮是否工作开始。在App.test.js 文件中添加这个代码段。

// src/App.test.js
import { render, screen, fireEvent } from "@testing-library/react";
import App from "./App";

describe("Counter component test suite", () => {
  test("displays the heading", () => {
    render(<App />);
    expect(screen.getByRole("heading").textContent).toBe("COUNTER");
  });

  test("increment button works", () => {
    render(<App />);
    const count = screen.getByTestId("count");
    const incrementBtn = screen.getByTestId("increment");
    expect(count.textContent).toBe("0");
    fireEvent.click(incrementBtn);
    expect(count.textContent).toBe("1");
  });

  test("decrement button works", () => {
    render(<App />);
    const count = screen.getByTestId("count");
    const decrementBtn = screen.getByTestId("decrement");
    expect(count.textContent).toBe("0");
    fireEvent.click(decrementBtn);
    expect(count.textContent).toBe("-1");
  });
});

这个代码段 "点击 "incrementdecrement 按钮,以检查计数值是否被递增或递减。这是对计数值的断言。通过在终端运行npm test 来运行测试。

PASS  src/App.test.js (5.799 s)
  Counter component test suite
    √ displays the heading (432 ms)
    √ increment button works (77 ms)
    √ decrement button works (48 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        12.097 s
Ran all test suites related to changed files.

在这种情况下,测试通过。欢呼吧!该片段显示,react-testing-library 模拟了用户在应用程序上的点击事件,并验证了测试的DOM状态是否改变为这些断言中的预期。现在你可以进入下一节,学习如何将你的测试与持续集成管道集成。在这种情况下,我们将使用CircleCI。

集成CircleCI

CircleCI是一个平台,帮助软件团队通过持续集成和持续部署(CI/CD)的原则自动构建、测试和部署。

在你项目的根文件夹中,创建一个.circleci 目录,并在其中添加一个config.yml 文件。把这个代码片段添加到配置文件中。

# .circleci/config.yml
version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/node:14.17.1
    steps:
      - checkout
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: install dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Hooks component tests
          command: npm test
      - store_artifacts:
          path: ~/repo/class-components-to-react-hooks

提交并推送更改到存储库。然后到CircleCI仪表板

打开项目页面,其中列出了与你的GitHub用户名或组织相关的所有GitHub仓库。在本教程中,点击class-components-to-react-hooks 。选择Set Up Project

Setting up project CircleCI

选择使用分支中的现有配置的选项main

Setting up CircleCI

Voila!在CircleCI仪表板上展开构建工作流程的细节,以验证所有的步骤是否成功。

Successful pipeline execution

展开Hooks component tests 构建步骤,验证Hooks测试是否成功运行。

Successful tests execution

现在,当你对你的应用程序进行更改时,CircleCI将自动运行这些测试。

总结

在本教程中,你已经了解了React Hooks以及它们在基于类的组件中的作用。我描述了使用Hooks的优点和缺点,以及如何使用不同类型的Hooks来获得与基于类的组件相同的结果。最后,你能够利用你对Hooks的了解来编写一个自定义Hook组件。你在计数器应用程序中使用了自定义Hook,并为它写了测试。