面试必备之函数组件,哈!无状态,纯函数,省事多了

159 阅读10分钟

哈哈 会爬树的金鱼 ,树上的金鱼呦😀

面试官:为啥前端框架都走向函数式了,和后端一样使用类不好吗?是放弃面向对象了吗?

我:前端框架提供了类组件和函数式两种写法,用那种的可以的

面试官:为啥推荐函数式呢?

我:官方建议的,也可以不用

面试官:那为啥官方不建议用类组件呢?

我:。。。。。

640.gif

背下来着怼回去!!!

this 导致混乱🤦‍♂️

本人被他人的 this 坑过很多次 😤╮(╯﹏╰)╭

在类组件中,this 指向组件实例。但是 this 的指向是可变的,在异步处理时很容易导致 this 变化,从而取到错误的值 比如:

  1. 在定时器或异步请求回调中,this 指向 window。因为这些异步函数是在全局作用域中执行的,所以 this 默认指向 window。
  2. 在事件处理函数中,this 指向触发事件的 DOM 元素。因为函数是在事件对象的作用域中调用的,所以 this 指向该 DOM 元素。
  3. 异步函数中 this 指向已经销毁的组件,导致销毁的组件上下文引用还存在

这些情况都会造成在异步处理时,取到的 this 已经变为其他值,无法正确访问原组件实例的 state 和 props,所以需要开发者时刻注意 this 指向,函数组件不涉及实例或 this 指向问题。

它只是一个普通的 js 函数,调用时会创建一个新的闭包,所以不存在异步过程中 this 变化的问题。 从架构层上看使用类组件还有以下问题

  1. 高内聚低耦合。this 指向可变会造成组件内部与异步逻辑之间的高耦合,增加了理解和维护组件的难度。
  2. 稳定和可预测。this 指向可变使得组件的行为在异步处理时变得不稳定和不可预测,这增加了 bug 的可能性。
  3. 简洁和容易理解。开发者不仅需要关注组件本身的逻辑,还需要关注异步过程中 this 的变化,这增加了组件的复杂度,使其难以理解。

类组件复杂度更高😢

多层汉堡,快乐多倍👻

类组件采用面向对象的编程思维方式。它将 UI 看作对象,通过定义类和继承来构建组件树。但是过度依赖继承容易会导致以下问题:

  1. 组件过重。组件会聚合许多无关的功能和状态,增加理解和维护成本。
  2. 功能难以拆分。继承会造成组件之间高度耦合,功能难以合理拆分成自包含的小组件。
  3. 性能损耗。组件实例之间的父子关系使得重新渲染时难以进行优化,导致不必要的性能损耗。

相比之下,函数组件采用面向过程的编程思维方式。它将 UI 看作一系列的操作和函数调用,通过自顶向下的方式逐层构建 UI。这避免了上述问题:

  1. 组件精简。函数组件可无状态,所以只聚焦于 UI 渲染即可,非常轻量级和模块化。
  2. 易于拆分。函数组件是独立的实体,可以方便拆分成多个自包含的小组件。
  3. 更高性能。函数组件不涉及组件实例,所以实现更高的性能。
  4. 函数组件是独立的,不依赖继承,所以具有更低的耦合度和更高的内聚度。
  5. 模块化。函数组件天然具有高度模块化,有利于实现松耦合的组件系统。
  6. 易维护和理解。没有继承和组件实例,函数组件有更简洁的结构,更易于被维护和理解。

代码量少,更加易读

找点时间,找点空间,早点回家看看😘

从前端开发者的角度看,函数组件相比类组件更易读和理解,能给项目带来更多收益。

  1. 函数组件的代码量更少。 函数组件只是一个普通函数,没有状态、生命周期和 this 指向等概念。所以相比类组件,它的代码结构更加精简。这使得开发者可以更集中精力关注 UI 的渲染逻辑,更易理解组件的整体行为。 相比之下,类组件需要定义状态、生命周期方法和事件处理函数等,代码结构复杂得多。这增加了阅读和理解组件代码的难度,开发效率会相对较低。

  2. 函数组件的逻辑更简单清晰。 没有状态变化和生命周期方法,函数组件仅聚焦于输入 props 和输出 UI ,这使得开发者一眼就可以看清组件的逻辑和职责,更易理解其工作原理,而在类组件中,需要跟踪状态的变化、生命周期方法的调用顺序等,这增加了理解组件行为的难度,相关逻辑也易失去简单和清晰性。

  3. 更简洁的代码结构。没有状态、生命周期等概念,开发者可以更集中精力在 UI 渲染上。

  4. 更高的开发效率。简洁的结构和清晰的逻辑使得开发者可以更快速地理解和编写函数组件,调试和维护的难度也更小。这提高了开发效率和体验。

  5. 更少的 Bug 可能。简单稳定的结构使得函数组件更不易出错,这也减轻了开发者的心智负担。

容易测试😀

能跑,测试用例全过🤣

在 Jest 等测试框架中,函数组件相比类组件更易测试,更易设计测试用例和实现测试。 主要原因有:

  1. 无状态简单结构。没有状态变化和生命周期方法,函数组件具有简单稳定的结构。这使得可以更容易设计足够的测试用例覆盖所有执行路径,实现高的测试覆盖率。
  2. 纯函数特征。函数组件拥有纯函数的特征,给定同样的输入必定产生同样的输出。这使得测试结果更加可预测,也更易在测试中对比预期结果。
  3. 高度隔离。函数组件天然具有高度隔离性,在每次测试调用时都会创建一个全新的组件实例。这使得测试环境不会被外部状态或副作用污染,测试结果更加可靠。

相比之下,测试类组件会复杂得多:

  1. 状态和生命周期方法需要测试,产生更多测试用例,覆盖率较难达到 100%。
  2. 组件实例在多次 render 之间保持不变,无法完全隔离测试环境,导致测试结果可靠性较差。
  3. 模块高耦合,难以设计足够的测试用例覆盖所有执行路径。
  4. 状态变化可能导致同样的输入产生不同输出,测试结果不太可预测。
  5. 生命周期和状态变化增加了 debug 难度,需要跟踪更多变量,debug 周期较长。

另外,从使用者角度看,测试函数组件可以带来更好的体验:

  1. 编写测试用例更简单快速,提高开发效率。
  2. 测试更加稳定可靠,减少反复测试的频率,提供更加自信的测试结果。
  3. 测试覆盖率更高,可以更大限度地保证组件的质量和正确性。
  4. debug 更简单,可以更快速地定位和修复测试失败的原因。

前端单线程不该有过于复杂的逻辑,虽然 v8 内核速度很快🏌️

卡顿?有可能是配置不够升级硬件即可

函数组件只是一个普通函数,没有状态、和组件实例等概念。所以,它的结构更加简洁,逻辑也更加简单。这有更加利于在单线程环境中获得更高的性能:

  1. 更少的状态变化。没有状态,所以不需要跟踪状态的变化,这减少了线程的工作量,释放出更多资源用于其他任务。
  2. 类实例化也需要占用线程资源,函数组件可以避免这部分开销。
  3. 更少的变量持续化。没有组件实例,所以不存在跨渲染的变量,这减少了内存的使用量。

相比之下,类组件的结构及逻辑都复杂得多:

  1. 状态的变化需要跟踪和更新,这耗费较多的线程资源。
  2. 组件实例需要在渲染之间持续存在,这增加了内存的使用量。
  3. 较复杂的结构使得框架难以实现理想的组件重用和渲染优化。

所以,在单线程环境下,函数组件拥有明显的性能优势。 另外,从框架作者的角度看,函数组件也更易于性能优化:

  1. 无状态,所以可以跳过状态比较等逻辑,直接返回 UI。这减少了组件更新的开销。
  2. 无实例,所以不存在跨渲染的变量,内存使用更少,GC 压力也更小。

函数式带来收益更高

性能提升低于50%?性价比过低交给硬件即可,没主动优化必要性,让市场和时间帮忙优化😘

函数组件只是一个普通函数,没有内部状态的概念。所以,它需要通过 Context 或其它状态管理方案共享状态。 这有以下优点:

  1. 避免组件树过深。不需要通过 props 一层层传递状态,所以可以避免组件嵌套过深的问题。这使得 UI 层级结构更加扁平合理。
  2. 状态共享更简单。通过 Context 等方案可以直接共享状态给深层子组件,管理起来更加简单。
  3. 每个组件有自己的状态。采用外部状态管理方案,每个组件可以维护自己的状态,状态之间可以相互独立。
  4. 更易于重构。外部状态使得组件结构可以更加灵活变化,因为状态不再依赖组件树。这有利于未来的重构。

相比之下,类组件需要通过 props 在组件树中传递状态,就存在以下问题:

  1. 组件树更深。需要通过 props 一层层传递状态到深层子组件,这使得组件树往往更加深。
  2. 状态共享较复杂。要在不同层级共享状态,需要在每个层级手动传递,实现起来比较复杂。
  3. 状态相互依赖。子组件的状态依赖父组件传递的 props,这使得状态之间存在相互依赖,不易重构。
  4. 每个组件都可以有自己的状态,这使得状态管理比较混乱,也不易维护。

所以,从状态管理的角度看,函数组件具有更清晰合理的方案:

  1. 简单扁平的组件树。有利于页面的渲染性能和用户体验。
  2. 外部状态管理。这是函数式编程的理念,有利于管理复杂状态和大规模应用。
  3. 状态隔离。有利于组件的重构和测试,也提高了应用的健壮性。
  4. 统一的状态管理方案。有利于大型项目的开发和协作。