React

类组件和函数组件区别

这里要注意,类组件hooks,这两个东西其实并不是一个概念。hooks只是一个工具集,用来增强函数组件的功能。真正要对比的应该是类组件函数组件
先来看看类组件函数组件的区别。

代码写法上的区别

这是最直观的区别,代码就长的不一样。随便列几个很常见的例子,这些特性在函数组件里都没有。

  • 类组件,顾名思义,它就是一个类,需要继承Class。
  • 类组件可以直接定义state
  • 类组件有生命周期方法
  • 类组件可以使用this获取到组件实例

    心智模型上的区别

    这是两个组件之间最大的区别,用https://overreacted.io/zh-hans/how-are-function-components-different-from-classes/中的话来说。
    函数式组件捕获了渲染所用的值。
    引用文章里的一个例子

    1. function ProfilePage(props) {
    2. const showMessage = () => {
    3. alert('成功关注 ' + props.user);
    4. };
    5. const handleClick = () => {
    6. setTimeout(showMessage, 3000);
    7. };
    8. return (
    9. <button onClick={handleClick}>关注</button>
    10. );
    11. }
    1. class ProfilePage extends React.Component {
    2. showMessage = () => {
    3. alert('成功关注 ' + this.props.user);
    4. };
    5. handleClick = () => {
    6. setTimeout(this.showMessage, 3000);
    7. };
    8. render() {
    9. return <button onClick={this.handleClick}>关注</button>;
    10. }
    11. }

    类组件以及函数组件实现了同一个逻辑。这两个组件都会接收一个props.user的属性,点击按钮,在3秒之后,会alert一条成功关注的信息。
    假如一开始传入的props的值是 { user: 'Hello' },然后点击关注按钮,在3秒之内,传入的props值变化了,变成了{ user: 'World' }。这两个组件将分别alert出什么内容?
    有过经验的同学肯定能轻松答出来。

    1. // 函数组件会打印
    2. '成功关注 Hello'
    3. // 类组件会打印
    4. '成功关注 World''

    为什么会这样呢?(这里注明一下,这个例子跟React框架无关,只是js的基本特性,在任何用js编写的代码中都可以复现)
    在React的类组件中,props虽然是不变的,但是this永远是可变。当有异步的事件触发,它获取到的props或者state永远都是最新的。当然也有办法去解决。
    比如可以重新定义一个数据来保存props

    1. handleClick = () => {
    2. const {user} = this.props;
    3. setTimeout(() => this.showMessage(user), 3000);
    4. };

    但这种方式太过繁琐,各种定义的数据非常不够优雅。
    或者把事件都写到渲染函数render中

    1. class ProfilePage extends React.Component {
    2. render() {
    3. const props = this.props;
    4. const showMessage = () => {
    5. alert('成功关注 ' + props.user);
    6. };
    7. const handleClick = () => {
    8. setTimeout(showMessage, 3000);
    9. };
    10. return <button onClick={handleClick}>关注</button>;
    11. }
    12. }

    这个方法其实函数组件的原理,props变化之后,组件虽然重新渲染了,但是老的props通过闭包保存了下来,然后被打印出来。
    写了这么多,只是为了论证那句话,函数式组件捕获了渲染所用的值。

    为什么要使用函数组件+hooks

    这一点很多人可能觉得没必要,觉得官方出的东西,跟着用就好,写着也挺顺手的,还管啥为什么呢?
    关于这一点,最重要的其实就是学习大佬们的思维,为什么要做出一个hooks来?肯定是为了解决一些原来的开发过程中的问题。React团队的设计层面的思路,能够在一定程度上代表当前业界在框架设计领域里最佳实践。
    接下来会列出几个类组件的几个痛点。(好和坏都是比较出来的,这些痛点只是相比于函数组件+hooks而言,技术在不断发展,技术的迭代都是正常的趋势)

    1.函数组件的写法更轻量,更加灵活

    在函数组件中,不需要去继承一个class对象,不需要记忆那些生命周期,不需要固定的把数据定义在state中。函数作为js中的一等公民,函数式编程方式可以更加灵活的去组织代码。

    2.类组件存在自身的缺陷

    一个其实就是上面一节写到,如果需要一个只跟着视图走的数据,不能直接使用props或者state
    还有一个是最常见的,在React中,如果定义一个方法,必须使用bind或者箭头函数去约束这个方法的this的作用域。
    这两个问题虽然都能解决,但是本质上,都是通过代码实践的方式去解决类组件自身的缺陷。
    但是在函数组件中,不会有这种问题,通过闭包的方式,在一次渲染中,组件的**props****state**是保持不变的,而且传递的方法本身就是已经被约束作用域了。

    3.逻辑是分散的,而且难以复用

    这个痛点其实跟vue2是一模一样的,React的类组件和vue2的开发模式都是类似。
    vue2和vue3区别的图来举例
    2021-05-23-09-51-42-716834.png
    这里的不同颜色其实就是不同逻辑,这图可以分成数据,事件方法,生命周期,模板四块内容。
    可以看到,一个逻辑在类组件里其实是分散的,拿React来说,数据需要定义在state里,然后需要编写相关的事件方法,再在生命周期里进行逻辑的初始化,组件更新时候的处理,最后在模板里写jsx
    如果是去维护这个代码,会很痛苦,为了查看一个逻辑,要上下翻,找出各自的数据,方法,生命周期和模板。
    而且这种方式的代码,很难被复用,抽离重复逻辑经历过mixin,HOC & render-props。这两种方式都有一个最大的问题,那就是props的来源不够清晰。mixin就不说了,都是泪,只能一个个去找。HOC嵌套层级一多,也会很难确定来源,而且HOC的学习成本也相对比较高,对于新手不太友好。
    但是如果使用hooks,还是用一张vue3中组合式API的图来说明,跟使用hooks的结果是类似的。
    2021-05-23-09-51-42-936248.png
    每块逻辑都是一个整体,非常清晰明了。可以把这些逻辑都抽离出去,只是在这个组件当中引用即可。
    逻辑复用就不用说了,现在那个React项目里没有好多自定义的hooks

    4.hooks更贴合React的基本理念

    React的设计理念 里第一条
    React的核心理念之一,相同的参数输入应该产生相同的输出。简单说,它应当是一个简单的纯函数。
    可以拿之前说到的心智模型上的区别中的例子来说明,传入一个props参数{ user: 'Hello' },希望关于这个参数的所有事件都强依赖于这条数据。它不应该像类组件一样,传入的参数和得到的输出不一致。
    但是函数组件可以做到,重复引用一下上面的一句话,在一次渲染中,组件的**props****state**是保持不变的

    hooks的不足

    谈论一个技术,不能太过于片面,一定要保持辩证思维,理性分析。上面说了很多hooks的优点,但是它依然存在着不足。

    1.比较大的心智负担

    同样是因为上面那句话,在一次渲染中,组件的**props****state**是保持不变的。这个特性导致的闭包陷阱是现在开发中最常见的一个问题。需要时刻注意是否已经给hooks添加了必要的依赖项。在封装一些功能相对复杂的组件时,useEffect的重复渲染问题处理有时候会非常棘手,而且不易调试。
    这个特性在对函数组件进行性能优化时也是会带来很大的麻烦,因为每次propsstate数据变化,都会导致函数组件中所有内容的重新渲染。需要通过memouseMemouseCallback这些方法手动去减少组件的render。当一个组件结构比较复杂,嵌套较多时,依赖项问题的处理也很让人头疼。
    这些点给开发者带来的学习hooks的成本相比较类组件来说会更大。

    2.需要更严格的开发规范

    函数式编程能够带来很大的灵活度,这个灵活度在开发当中是一个双刃剑。它能更好的去解决一些复杂的需求,但是它在一个多人协作开发的项目中并不是一个好的事情。
    一些大型的项目,最重要的一定是编程的规范,eslint可以限制语法规范,但是限制不了实现需求的规范,一人一个风格的代码对于一个项目来说就是个灾难,对于后续的维护,公共方法的抽离来说将带来很大的麻烦。
    所以,需要开发者制定一个具体的开发规范,并在开发的时候严格遵守。

    3.hooks并不能完全代替类组件

    虽然有了很多hooks方法,用来增强函数组件的功能。比如useState可以让函数组件维护自己的数据。有useEffect可以在一定程度上弥补函数组件没有生命周期的缺点。 :::tips 注意:useEffect并不是用来代替生命周期,它只是提供了一个类似生命周期的方法。两者其实本质上没有可比性。 ::: 想了解更多,可以看Dan的这篇博客,https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/,它可以帮你更好的理解hooks中的useEffect
    但是,还是不能拿hooks来完全代替类组件。(主要是部分生命周期还无法被代替)
    比如componentDidCatch这个获取组件错误的生命周期,在大部分的项目中,肯定会看到它的身影。还有其他的一些生命周期,可能在一些特殊的场景下还是需要用到。
    所以,在一段时间之内,hooks还是会跟类组件共存。

    总结

    越来越多的公司和团队早已经全面拥抱hooks,现在再谈论使用hooks的收益跟付出相比是否值得已经毫无意义,大的潮流已经指明了方向,今年新发布的正式版vue3也是借鉴了hooks的方式,实现了让开发者使用组合式api的方式组织逻辑代码。