前言

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) 次数 差值
第四次 200 766
第三次 100 369
第二次 50 267
第一次 10 168

第一次运行截图:
image.png

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

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

  1. import React, { useState, useEffect } from "react";
  2. const hooksComponent = () => {
  3. const [number, setNumber] = useState(0);
  4. useEffect(() => {
  5. const myIntel = setTimeout(() => {
  6. if (number === 200) {
  7. clearTimeout(myIntel);
  8. console.log(`hooks结束时间:${new Date().getTime()}`);
  9. } else {
  10. (() => {
  11. setNumber(number + 1);
  12. })();
  13. }
  14. }, 50);
  15. });
  16. return <div>{number}</div>;
  17. };
  18. export default hooksComponent;

类组件源码:

  1. import React, { PureComponent } from "react";
  2. export default class reactComponent extends PureComponent {
  3. constructor(props) {
  4. super(props);
  5. this.state = { number: 0 };
  6. }
  7. componentDidMount = () => {
  8. const myIntel = setInterval(() => {
  9. const { number } = this.state;
  10. if (number === 200) {
  11. clearTimeout(myIntel);
  12. console.log(`class结束时间:${new Date().getTime()}`);
  13. } else {
  14. this.addNumber();
  15. }
  16. }, 50);
  17. };
  18. addNumber = () => {
  19. const { number } = this.state;
  20. this.setState({ number: number + 1 });
  21. };
  22. render() {
  23. const { number } = this.state;
  24. return <div>{number}</div>;
  25. }
  26. }

父组件调用源码:

  1. import React, { PureComponent } from "react";
  2. import ReactComponent from "./classTest.jsx";
  3. import HooksComponent from "./hooksTest";
  4. class Test extends PureComponent {
  5. constructor(props) {
  6. super(props);
  7. console.log(`开始时间:${new Date().getTime()}`);
  8. }
  9. render() {
  10. return (
  11. <div>
  12. <HooksComponent />
  13. <ReactComponent />
  14. </div>
  15. );
  16. }
  17. }
  18. export default Test;

另外大家也可以前往 https://codesandbox.io/s/bold-wilson-1f1gm?file=/src/App.js 查看源码和运行结果。
最后,文中如有不当之处,欢迎批评指正,谢谢!