Hook 组件 VS 类组件

2,753 阅读4分钟

前言

React Hook 正式发布是在2019年2月6日 React 版本 16.8 引入的新特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。说简单点,Hook 组件其实就是有状态的函数式组件。因此函数组件也由SFC(stateless functional component )更新成了 FC(function component)。本文编写的主要目的就是对比 Hook 组件和类组件的差别,让你对Hook组件有进一步的了解。

使用方式

命令式 VS 声明式

在 Class 组件中,通常的写法是在生命周期检查 props 和 state,如果改变或者是符合某个条件就触发xxx副作用。在函数式组件中使用 Hooks 的写法是组件有xxx副作用,这个副作用依赖的数据是 props 和 state。一个是被动触发,一个是主动声明。

去生命周期化

在 hooks 组件中使用 useEffect 取代了类组件生命周期的调用,使得“生命周期”变成了一个“底层概念”,因此开发者能够将精力聚焦在更高的抽象层次上。 react 开发者认为生命周期使得逻辑过于分散,且代码重复率较高;使用 hooks 能够将逻辑进行聚合,代码量更少;但是对于复杂的交互逻辑,我个人认为使用生命周期函数会让结构更加清晰和易读。下面盗用一张图来对比说明: image.png

去 this 化

this 的问题: 1)需要使用 bind 方法绑定,bind 方法会创建一个新函数,影响性能 2)虽然可以使用箭头函数避免这种难看的写法,但仍然会创建一个新函数,对性能有影响 3)通过 this.props 每次都会取到最新的 props,有时候会导致视图错误的渲染 在 hooks 组件中,没有用 this 来获取 props,直接以参数的形式传递,因此只会在当前状态生效。

编译和压缩

实现相同的功能,类组件和 hooks 组件编译完后文件大小对比如下:

类组件编译截图

image.png

hook 组件编译截图

image.png

class 组件编译后比hook组件编译后多出 20 几行代码,原因是 class 组件需要继承 React.Component,需要生成一些辅助函数; 另外,对于构建工具来说,class 不能很好的压缩,并且会使热重载出现不稳定的情况。 综上,在代码压缩方面,函数组件会比类组件稍具优势。

性能

虽然 React 开发者 Dan Abramov 在博客中的解释说 “性能主要取决于代码的作用,而不是选择函数式组件还是类组件。在我们的观察中,尽管优化策略各有略微不同,但性能差异可以忽略不计”。但不少网友担心说类组件的 function 不会被重复创建,直接通过 this.func 可以进行调用,而在 hooks 组件中只要组件被渲染一次, function 就会被重新创建一次,造成性能上的消耗,那我们举个例子来对比下:

要对比函数组件和类组件当中函数的渲染性能消耗,其实质就是对比静态函数动态函数(也就是 hook 中的闭包)的性能差异,但是如果只是几次的函数渲染,那差异也是可以忽略不计的,那我们就模拟在极端情况下,类组件和函数组件中函数分别被创建了10、50、100、200次的时间差,对比表格如下:

时间(ms)次数差值
第四次200766
第三次100369
第二次50267
第一次10168

第一次运行截图:

image.png

最后一次运行截图: image.png

由此可以看出,假使你在 hook 组件中定义了 200 个函数方法(这种情况几乎极少,甚至不可能出现),那他和类组件的渲染时间差也不会超过 1s,而且 hook 还提供了譬如 useMemo、useCallBack 等性能优化的方法。所以在平常开发中我们完全可以不用担心 hook 创建函数时的性能问题。 也正如 react 官方说的那样,函数组件和类组件在渲染上性能差异不大,在现代浏览器中,闭包和类的原始性能只有在极端场景下才会有明显的差别。 下面贴上此次测试的源码给大家: 函数组件源码:

import React, { useState, useEffect } from "react";
const hooksComponent = () => {
  const [number, setNumber] = useState(0);
  useEffect(() => {
    const myIntel = setTimeout(() => {
      if (number === 200) {
        clearTimeout(myIntel);
        console.log(`hooks结束时间:${new Date().getTime()}`);
      } else {
        (() => {
          setNumber(number + 1);
        })();
      }
    }, 50);
  });

  return <div>{number}</div>;
};
export default hooksComponent;

类组件源码:

import React, { PureComponent } from "react";
export default class reactComponent extends PureComponent {
  constructor(props) {
    super(props);
    this.state = { number: 0 };
  }

  componentDidMount = () => {
    const myIntel = setInterval(() => {
      const { number } = this.state;
      if (number === 200) {
        clearTimeout(myIntel);
        console.log(`class结束时间:${new Date().getTime()}`);
      } else {
        this.addNumber();
      }
    }, 50);
  };

  addNumber = () => {
    const { number } = this.state;
    this.setState({ number: number + 1 });
  };

  render() {
    const { number } = this.state;
    return <div>{number}</div>;
  }
}

父组件调用源码:

import React, { PureComponent } from "react";
import ReactComponent from "./classTest.jsx";
import HooksComponent from "./hooksTest";

class Test extends PureComponent {
  constructor(props) {
    super(props);
    console.log(`开始时间:${new Date().getTime()}`);
  }
  render() {
    return (
      <div>
        <HooksComponent />
        <ReactComponent />
      </div>
    );
  }
}

export default Test;

另外大家也可以前往 codesandbox.io/s/bold-wils… 查看源码和运行结果。 最后,文中如有不当之处,欢迎批评指正,谢谢!