使用 react hooks 而不使用 class 的6个原因(译)

384 阅读12分钟

原文地址 blog.bitsrc.io/6-reasons-t…

React hooks have been around for some time now, yet many React developers are not actively using them. I see two primary reasons behind this. The first reason is that many React developers are already involved in a large project, which requires a significant effort to migrate the entire codebase. The other reason is the familiarity with React Classes. With the experience, it feels more comfortable to keep using Classes.


React Hooks 已经出现一段时间了,但是许多React开发人员并没有积极地使用它们。我认为这背后有两个主要原因。第一个原因是许多React开发人员已经在一个大型项目中进行开发,如果要使用 React Hooks,就意味着需要大量的工作来迁移整个代码库。另一个原因是已经习惯了Class的使用,使用Class更舒服。

In this article, we’ll look into five reasons why you should consider React Hooks.


在这篇文章中,我们将探讨6个(原文写的5)你应该考虑React Hooks的原因。

1. You don’t have to refactor a functional component into a class component when it grows 当函数组件进行功能扩展时,你不必将其重构为Class组件

Usually, there are times when a React component starts with a functional component, which only depends on the props and later evolves into a Class component with having a state. Changing from a functional component to a class component requires a little bit of refactoring, depending on how complex the component is.


通常情况下,React 组件以函数组件的形式初始,它只具备Props,之后需要扩展为带有 state 的 Class 组件。从函数组件更改为Class组件,就需要进行一些重构,重构的难度取决于组件的复杂程度。

With React Hooks, since functional components have the capability of tapping into the state, the refactoring effort will be minimal. Let s consider the below example, a dumb component that shows a label with a count.


有了React Hooks之后,因为函数组件本身已经具备了 state 能力,再进行重构时,工作量将变小。看一个示例,一个用于展示计数功能的组件。

export function ShowCount(props) {
  return (
    <div> 
      <h1> Count : {props.count} </h1>
    </div>
  );
}

Let s say we need to increment the count with mouse clicks and let s assume this only affects this particular component. As the first step, we need to introduce the state to our component. Let s take a look at how we would do that with a class-based approach.


假设我们需要增加鼠标点击的计数功能,并且只影响特定的组件。第一步,我们需要将 state 引入组件。使用基于Class的方法来实现。

export class ShowCount extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count0
    };
  }
  componentDidMount() {
    this.setState({
      countthis.props.count
    })
  }

  render() {
    return (
      <div> 
        <h1> Count : {this.state.count} </h1>
      </div>
    );
  }
  
}

The same component will look like the following if we use Hooks.


如果我们使用 Hooks ,如下所示。

export function ShowCount(props) {
  const [count, setCount] = useState();

  useEffect(() => {
    setCount(props.count);
  }, [props.count]);

  return (
    <div>
      <h1> Count : {count} </h1>
    </div>
  );
}

2. You don’t have to worry about “this” anymore. 你不用再担心 “this”

Classes confuse both people and machines 类混淆了人类和机器

The above sentence is from React documentation. One of the reasons for this confusion is this keyword. If you are familiar with JavaScript, you know that this in JavaScript doesn t work exactly like in other languages. When it comes to React Hooks, you don t have to worry about this at all. This is good for beginners as well as experienced developers.


上面的句子来自React文档。造成这种混淆的原因之一就是 this。如果你熟悉JavaScript,你就会知道在JavaScript中 this 和在其它语言中是不同的。在React Hooks中,你根本不必担心这个。这对初学者和有经验的开发人员都很好。

According to the above example, you can see that we no longer have to use this while accessing the state. This makes it less confusing for everyone.


根据上面的例子,你可以发现,我们在访问 state 时不再需要 this 。这样不会让使用者感到困惑。

3. No more method bindings. 不再有很多的方法绑定

Now for the above same ShowCount component let s introduce a method to update the count of the state when the user clicks on the label.


现在,对于上面的ShowCount组件,我们需要增加一个方法,用于记录用户点击标签的计数状态。

export class ShowCount extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
    this.handleClickEvent = this.handleClickEvent.bind(this);
  }
  componentDidMount() {
    this.setState({
      count: this.props.count
    });
  }

  handleClickEvent() {
    this.setState({count: this.state.count + 1});
  }

  render() {
    return (
      <div>
        <h1 onClick={this.handleClickEvent} > Count : {this.state.count} </h1>
      </div>
    );
  }
}

We have introduced handleClickEvent method. To use it, first, we have to bind it to this of the Component.


对于 handleClickEvent 方法。在使用它之前必须将它绑定到组件的this上。

We have to do this because the execution context is different when the method gets executed. For a beginner developer, this might be a bit hard to understand.


我们必须这样做,因为当方法被执行时,执行上下文是不同的。对于初学者开发人员来说,这可能有点难以理解。

Instead of binding all the methods, there are some syntax proposals where you can get around this. For example, we can rewrite the function to an arrow function.


除了绑定所有的方法以外,也可以通过一些语法来解决这类问题,比如将函数改为箭头函数。

handleClickEvent = () => {
  this.setState({count: this.state.count + 1});
}

Let s see how we can implement the same functionality with Hooks.


让我们看看如何用 Hooks 实现相同的功能。

export function ShowCount(props) {
  const [count, setCount] = useState();

  useEffect(() => {
    setCount(props.count);
  }, [props.count]);

  function handleClickEvent() {
    setCount(count + 1);
  }

  return (
    <div>
      <h1 onClick={handleClickEvent}> Count : {count} </h1>
    </div>
  );
}

As you can see, we have only added the function. Also, you might notice that when we use the event handler, we have removed this in the template.


如你所见,我们只添加了函数。另外,你可能会注意到,我们在事件触发的位置,去掉了 this。

onClick={ handleClickEvent }

4. Easier to decouple logic from UI, making both more reusable. 更容易从UI中解耦逻辑,使两者更可重用

Using hooks, logic and UI are easier to separate. No need for HOC or render props. Hooks do it elegantly with less boilerplate and more intuitive compositions of UI and logic.


使用Hooks,逻辑和UI更容易分离。不需要使用 HOC (Higher-Order Component,高阶组件) 或者 render props。Hooks 能更好的实现更少的模板文件和更直观的UI、逻辑组合。

This elegant separation is especially crucial when sharing components using tools and platforms like Bit (Github) as each (independently shared) component is much easier to understand, maintain, and reuse across different apps.


当使用像Bit (Github)这样的工具和平台分享组件时,这种优雅的拆分尤其重要,因为每个(独立共享的)组件更容易理解、维护,并且在不同应用程序中更易被重用。

5. Keep related logic in the same place. 将相关联的逻辑放在相同的位置

Complex components become hard to understand 复杂的组件变得难以理解

With the class-based approach, we have different life cycle methods such as componentDidMountand componentDidUpdate etc. Let's consider a situation where subscribing to services A and B in componentDidMount and unsubscribing them on componentWillUnmount. With time, there will be many logics included in both life cycle methods, and it will be hard to keep track of which part of mounting is related in unmounting.


使用基于Class的方法,我们有不同的生命周期方法,比如 componentdidmount 和 componentDidUpdate 等等。我们考虑这样一种情况:在 componentDidMount 中我们定义服务 A 和 B ,在 componentWillUnmount 中我们关闭这些服务。随着时间的推移,在这两个生命周期钩子函数中。包含了许多逻辑,逐渐的很难再追踪到哪部分和这些挂载或者卸载的服务相关联。

To demonstrate this, let's create an RxJs based service to get the count. We will use this service to update the count in ShowCount example. Note that we will be removing the handleClickEvent as we no longer need to update the component on click events.


为了演示这一点,让我们创建一个基于RxJs的服务来获取计数。我们将使用该服务来更新ShowCount示例中的计数。注意,我们将删除handleClickEvent,因为我们不再需要在单击事件上更新组件。

import { Subject } from "rxjs";

export function getCounts() {
  const subject = new Subject();
  let count = 0;
  const interval = setInterval(() => {
    if (count > 10 || subject.isStopped) {
      clearInterval(interval);
      subject.complete();
    }
    subject.next(count++);
  }, 1000);

  return subject;
}
import { getCounts } from "./reactive-service";

export function ShowCount(props) {
  const [count, setCount] = useState();

  useEffect(() => {
    const countServiceSubject = getCounts();
    countServiceSubject.subscribe((count) => {
      setCount(count);
    });
    return () => {
      countServiceSubject.unsubscribe();
    };
  }, []);

  return (
    <div>
      <h1> Count : {count} </h1>
    </div>
  );
}

You can see that inside the useEffect we have included subscribing as well as corresponding unsubscribing logic. Similarly, if we need to introduce more service subscriptions or unrelated logics, we can add multiple useEffect blocks to logically separate different concerns.


你可以看到,在 useEffect 中,我们包含了服务的订阅和取消逻辑。类似地,假设,我们需要引入更多的服务或者一些不相关的逻辑,可以增加多个 useEffect,分别管理这些服务逻辑。

import { getCounts } from "./reactive-service";

export function ShowCount(props) {
  const [count, setCount] = useState();
  const [secondCount, setSecondCount] = useState(0);

  useEffect(() => {
    const countServiceSubject = getCounts();
    countServiceSubject.subscribe((count) => {
      setCount(count);
    });
    return () => {
      countServiceSubject.unsubscribe();
    };
  }, []);

  useEffect(() => {
    setSecondCount(secondCount + 1);
  }, []);

  return (
    <div>
      <h1> Count : {count} </h1>
      <h1> Second Count: {secondCount} </h1>
    </div>
  );
}

6. Sharing stateful logic between components. 组件之间共享state

With the class-based approach, it is hard to share stateful logic between components. Consider two components where both have to fetch, sort, and display data from two different data sources. Even though both components have the same functionality, it is hard to share the logic because these components have different sources and states.


使用基于Class的方法,很难在组件之间共享 state 逻辑。假设有两个组件,它们从两个不同的数据源请求、排序和展示数据。虽然两个组件具有相同的功能,但很难共享逻辑,因为这些组件具有不同的源和状态。

While we can use render props and higher-order components to solve this, it will also introduce additional cost as we have to restructure our components, which will eventually make it harder to follow.


虽然我们可以使用渲染 props 和 HOC (高阶组件) 来解决这个问题,但它也会引入额外的成本,因为我们必须重构我们的组件,而结果是这个组件更加难以维护。

What React Hooks offers. React Hooks提供了什么

With Custom React Hooks you can extract these reusable stateful logics and test them separately.


使用自定义的React Hooks,你可以将带有状态逻辑的部分进行抽离,并且可以对它们进行独立测试。

We can extract a custom hook from the ShowCount example.


我们可以从ShowCount示例中抽离一个自定义 Hook。

import { useState, useEffect } from "react";

export function useCount(serviceSubject) {
  const [count, setCount] = useState();

  useEffect(() => {
    serviceSubject.subscribe((count) => {
      setCount(count);
    });
    return () => {
      serviceSubject.unsubscribe();
    };
  }, [serviceSubject]);

  return [count, setCount];
}

Using the above custom hook, we can rewrite the ShowCount component as follows. Notice that we have to pass the data source to the custom hook as a parameter.


使用上面的自定义 hook,我们可以重写ShowCount组件,如下所示。请注意,我们必须将数据源作为参数传递给自定义的 hook。

import { useCount } from "./use-count";

export function ShowCount(props) {
  const [count, setCount] = useCount(props.serviceSubject);

  useEffect(() => {
    setCount(-1);
  }, [setCount]);

  return (
    <div>
      <h1> Count : {count} </h1>
    </div>
  );
}

Note that we invoke getCounts in a parent component rather than in ShowCount component. Otherwise serviceSubject will have a new value each time it runs ShowCount and we wouldn t get the result we expect.

注意,我们是在父组件中调用 getCounts,而不是在ShowCount组件中。不然,serviceSubject 每次调用 ShowCount时都会有赋一个新值,我们将不会得到我们期望的结果。

Conclusion 总结

While there are many reasons to switch to React Hooks, I have mentioned the most compelling reasons which made me change to React Hooks. If you look at the official documentation, you will see that there are many interesting functionalities with React Hooks. Please let me know in the comments about your journey to React Hooks.


虽然切换到React Hooks有很多原因,但我已经提到了让我改用React hook的最重要的原因。如果你查看官方文档,就会发现React Hooks有许多有趣的功能。请在评论中告诉我你的React Hooks之旅。

You can find the completed source code here.


您可以在这里找到完整的源代码。