相比于 class 组件,hooks到底解决了什么问题?

写在前面

相信使用 react 的小伙伴,都使用过 Class 组件和函数式组件。

目前来说,函数式组件比较流行,基本上新开发的项目都是以函数式组件为主。

那相比于这两种方式,最直观的就是函数式组件引入了 hooks,在引入 hooks 之后函数式组件变得更加灵活,所以这个问题基本上就是考察你对两种组件的理解,今天就以我个人的理解给大家分享一下,希望可以帮助到大家!

1、状态逻辑复用

class 组件中状态逻辑难以复用,常常需要使用高阶组件或 Render Props。

hooks 引入了 useState、useReducer 等钩子,使得状态逻辑可以更容易地在不同组件之间共享和复用。

下面我们来举个栗子看看:

假设我们有两个组件 Counter1 和 Counter2,它们都需要使用 count 状态和 increment 函数。

类组件:

在这个例子中,两个组件有相同的状态逻辑,但由于类组件的特性,我们无法很好地抽象出这些逻辑进行复用。

class Counter1 extends React.Component {
  state = { count: 0 };

  increment = () => {
    this.setState(prevState => ({ count: prevState.count + 1 }));
  };

  render() {
    return (
      <div>
        <p>Counter 1: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

class Counter2 extends React.Component {
  state = { count: 0 };

  increment = () => {
    this.setState(prevState => ({ count: prevState.count + 1 }));
  };

  render() {
    return (
      <div>
        <p>Counter 2: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

使用hooks:

使用 hooks,我们可以将这些逻辑抽象为一个自定义 hook,这样在Counter1 和 Counter2中可以复用一个函数,而不需要重写多个相同的逻辑。

import { useState } from 'react';

function useCounter() {
  const [count, setCount] = useState(0);

  const increment = () => setCount(prevCount => prevCount + 1);

  return { count, increment };
}

function Counter1() {
  const { count, increment } = useCounter();

  return (
    <div>
      <p>Counter 1: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

function Counter2() {
  const { count, increment } = useCounter();

  return (
    <div>
      <p>Counter 2: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

2、生命周期管理

class 组件中的生命周期方法(如 componentDidMount、componentDidUpdate 等)往往包含多个不相关的逻辑,导致代码不够清晰。hooks 提供了 useEffect 钩子,根据依赖项的不同情况可以模拟这几个声明周期函数,也就是一个 hooks 可以实现多个声明周期。

1.模拟 componentDidMount

componentDidMount():在组件被挂载到 DOM 后调用,通常用于发送网络请求、订阅事件等初始化操作。

使用useEffect:依赖项为空数组,初次渲染执行

  useEffect(() => {
    console.log('Component mounted');
    // 执行数据获取操作
    fetchData();
  }, []);

2.模拟 componentDidUpdate

componentDidUpdate(prevProps, prevState, snapshot):在组件更新后调用,通常用于处理更新后的操作,比如更新 DOM 或进行网络请求。

使用useEffect:依赖项为state,state变化时执行

  useEffect(() => {
    console.log('Component updated');
    // 执行其他副作用操作
    document.title = `You clicked ${count} times`;
  }, [count]);

3.模拟 componentWillUnmount

componentWillUnmount():在组件即将从 DOM 中移除时调用,用于清理工作,比如取消订阅、清除定时器等。

使用useEffect:在组件卸载时执行return,清理副作用

  useEffect(() => {
    return () => {
      console.log('Component unmounted');
      // 执行清理操作
      clearSubscription();
    };
  }, []);

3、性能优化

hooks 可以帮助避免不必要的组件重新渲染,通过 useMemouseCallback 等钩子进行性能优化。

 useMemo

用于记忆(缓存)计算结果,并在依赖项变化时重新计算。 这有助于避免不必要的重复计算,提高性能。 

    import React, { useMemo } from 'react';

    function ExpensiveComponent({ prop1, prop2 }) {
      // 使用 useMemo 缓存计算结果
      const result = useMemo(() => {
        // 进行昂贵的计算或处理
        return prop1 + prop2;
      }, [prop1, prop2]); // 依赖项数组

      return <p>Result: {result}</p>;
    }

useCallback

用于缓存回调函数,避免在每次渲染时重新创建。

接收一个回调函数和依赖项数组,返回缓存后的回调函数。

useCallback的依赖项参数用于指定哪些变量的变化会导致生成新的回调函数。

   import React, { useState, useCallback } from 'react';

   function MemoizedComponent() {
     const [count, setCount] = useState(0);

     const handleClick = useCallback(() => {
       // 使用 count 进行逻辑处理
       console.log(count);
     }, [count]);

     return (
       <div>
         <p>Count: {count}</p>
         <button onClick={() => setCount(count + 1)}>Increment</button>
         <ChildComponent onClick={handleClick} />
       </div>
     );
   }

   function ChildComponent({ onClick }) {
     // 使用缓存后的回调函数
     return <button onClick={onClick}>Click me</button>;
   }

在这个例子中,useCallback 缓存了 handleClick 回调函数,并且指定了依赖项数组为[count]。这意味着只有当 count 发生变化时,handleClick 才会生成新的回调函数。 

4、Tree-shaking

Tree-shaking 本身并不是 React Hooks 解决的一个问题,而是另一个与代码优化相关的概念。

Tree-shaking 指的是一种去除程序中未使用的代码的优化技术,可以减少最终构建产物的体积。

它通常在构建工具如Webpack、Rollup等中使用。

React Hooks与 Tree-shaking 并没有直接关系,但是由于 React 函数组件代码通常比相应的类组件更加精简,比如没有 this 指向,生命周期等问题,所以在进行 Tree-shaking 时,函数组件中未使用的代码更容易被shaking掉。

具体来说:

  1. 类组件存在语法开销:类组件需要构造函数、生命周期方法等,即使未被使用,这些语法也会存在于最终产物中。
  2. 函数组件语法更精简:Hooks 函数组件只有函数调用,语法更精简,tree-shaking更容易发挥优化作用。
  3. 自定义Hooks可分割代码:通过自定义Hooks将相关逻辑抽离,未使用的Hooks代码更容易被shaking掉。
  4. 渲染性能略优:由于函数组件整体较精简,React在渲染时稍微更高效,产物体积也相对更小。

所以虽然 Tree-shaking 不是 React Hooks 直接解决的问题,但由于 Hooks 函数组件语法上的优势,的确使得基于 Hooks 编写的 React 应用更容易受益于 tree-shaking 优化,进而获得更小的产物体积。但并不意味着使用类组件就无法获得 tree-shaking 优化。

总的来说,两者是两个不同的概念,Hooks主要解决的是代码复用、状态管理等React编程模型层面的问题,tree-shaking 则是代码层面的优化手段,恰好与 Hooks 风格高度契合。

关于 tree-shaking,大家可以看  Tecvan大佬的这篇文章。

Webpack 原理系列九:Tree-Shaking 实现原理-腾讯云开发者社区-腾讯云 (tencent.com)

总结

hooks 的出现 让 React 函数组件也能够更好的进行状态复用以及使用 useEffect 模拟生命周期的实现和更好的tree-shaking优化,还提高了代码的可读性、复用性和可维护性。

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JacksonChen_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值