前言
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 能够将逻辑进行聚合,代码量更少;但是对于复杂的交互逻辑,我个人认为使用生命周期函数会让结构更加清晰和易读。下面盗用一张图来对比说明:
去 this 化
this 的问题:
1)需要使用 bind 方法绑定,bind 方法会创建一个新函数,影响性能
2)虽然可以使用箭头函数避免这种难看的写法,但仍然会创建一个新函数,对性能有影响
3)通过 this.props 每次都会取到最新的 props,有时候会导致视图错误的渲染
在 hooks 组件中,没有用 this 来获取 props,直接以参数的形式传递,因此只会在当前状态生效。
编译和压缩
实现相同的功能,类组件和 hooks 组件编译完后文件大小对比如下:
类组件编译截图
hook 组件编译截图
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 |
第一次运行截图:
最后一次运行截图:
由此可以看出,假使你在 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;