为什么React需要Hooks

前言

如果你是一个React开发者,那么你经常会听到关于React hooks,也就是所说的钩子的讨论。而对于那些没有使用过React hooks的开发者来说,这只是React内置的函数,它们可以让我们让在functional组件内里做所有可以在class组件内做的事。

但是为什么要使用React hooks呢? 如果类组件可以完成我们希望它们做的所有事情,比如初始化状态,设置状态,访问组件的生命周期,创建上下文,调用引用等,那么我们为什么要为我们的展示性组件提供一种方法,做同样的事情呢?functional组件有什么特别之处?

好吧,事实证明,使用钩子为开发人员带来好处的原因有很多。首先是大型组件可能难以使用,在大型项目上工作的开发者们,组件的状态可能会越来愈多,愈来愈复杂,其大小经常会在组件生命周期方法中散布相同或相似的逻辑。第二个是方法重用,重用组件方法(尤其是复杂的逻辑)依赖于设计模式,例如高阶组件或渲染参数,这反过来又需要开发人员重新组织其组件层次结构,并可能导致组件层级混乱。最后,类语法对卡发者和编译器都造成了混淆,并且可能导致React所追求的纯粹的功能设计模式无法实现。

那么钩子如何解决这些问题呢?

组件大小

当涉及到组件的规模,大小时,钩子使用useEffect提取生命周期方法中的许多重复功能,并使用useState初始化,改变状态。例如,一个useEffect挂钩可以完成3种生命周期方法的工作:componentDidMount,componentDidUpdate和componentWillUnmount。通过指定一个可选的依赖项数组,您可以告诉useEffect在指定的状态引用中查找更改,然后再次运行该效果,等效于componentDidUpdate。通过指定一个空的依赖关系数组,您可以告诉useEffect仅运行一次componentDidMount。当从DOM中删除组件时,您需要执行一些数据清除操作(例如清除间隔或阻止api调用),您可以在return语句中为useEffect回调,该语句将在unmount- componentWillUnmount上运行。所有这些工作都可以通过一个useEffect调用来完成,这使其成为解决副作用的强大方法!

方法复用

在应用中其他地方重用组件逻辑时,我们概括这些方案的传统方式是使用渲染道具或HOC(高阶组件)。
渲染道具的工作方式是将带有独立JSX的自定义回调作为参数,传递给我们的组件。当我们希望相同的渲染组件的多个实例之间重用时,就可以这样。例如,如果我们想通过一个组件跟踪鼠标的位置,但想对该数据做不同的事情(例如在p标签而不是h1标签中显示位置),则可以通过指定渲染擦输来实现使用定制的JSX。

1
2
<MousePosition render = {position => <p>{position}</p>}></MousePosition>
<MousePosition render = {position => <h1>{position}</h1>}></MousePosition>

HOC(高阶组件)通过创建包装器组件或返回包装器组件的函数来实现,该函数使包装的组件可以访问某些数据或功能。如果多个组件正在执行相似但略有不同的工作,例如从相同来源获取某些数据但使用不同方法,则我们将希望使用此功能。想想Redux的connect()函数,它将包装好的组件连接到我们的商店。这里可能需要连接多个组件,但是状态和调度方法的需求却有所不同。因此,我们可以向我们的连接HOC提供自定义的mapStateToProps和mapDispatchToProps函数,以为包装的组件提供个性化数据。

1
export default connect(mapStateToProps, mapDispatchToProps)(SomeComponent);

尽管这些模式对于类组件非常有效,但它们也有缺点。特别是,它们要求开发人员重写其组件层次结构,并可能导致包装器地狱的第9个循环。 React hooks的替代方法是允许我们的可以调用以自定义hook的形式在各​​个组件之间共享。自定义钩子只是可组合的函数,可以像其他任何函数一样在组件之间导出和共享。可组合的意思是指具有多个部分(例如useState和useEffect调用),将它们组合为可重用模式并将其抽象为自己的单独功能的能力。这种模式在类组件中将是不可能的,因为做诸如获取数据或实例化状态之类的简单操作不能与调用它们的类方法分开。

性能与困惑

最后,大家对于类组件的性能其实没有什么担心。在2018年React Conference上,当React Hooks揭幕时,有人指出类组件对于机器和人类都很难理解。由于类语法只是JavaScript原型继承的一种语法糖,因此类的工作方式与其他编程语言不同。React团队注意到在生产环境中最小化类组件的问题,并且在实现热模块重新加载时存在不一致之处。此外,对于开发者,尤其是对那些刚接触类语法的开发者来说,可能很难理解’this’关键字的绑定是如何工作的,或者难以理解其他类的细微差别,例如在构造函数内部调用super()的目的。

由于React hooks使我们能够在功能组件内部使用所有相同的类组件功能,因此我们能够提高React代码的性能。此外,钩子具有简洁的语法,不仅与React的纯功能设计紧密相关,而且与JavaScript紧密相关。 React在其他基于组件的前端库(如Vue)之间的主要区别之一是它通过JSX和css模块将html,css和JavaScript耦合在一起。这意味着,如果你是JavaScript专家,那么您就是React(提供或获取实现细节)专家,而不是像过去那样依赖Vue在HTML,CSS和JS之间拆分文件的模板结构jQuery和Vanilla JS的DOM处理的概念。

尽管功能组件的性能略有提高,但性能不应成为使用React hooks的主要原因。它也不会在React中添加尚不存在的任何功能。其主要目的只是简单地将功能组件提升到与类组件相同的级别,从而通过更干净,更可维护的代码,更好地在组件之间重用方法以及更简单,更具声明性的使用方式,从而获得更好的DX(开发人员体验)。

话虽这么说,但我有一些同事发现React hooks在概念上不容易掌握,尤其是在了解useEffect,useState或useContext时。例如,很难看到应该如何将生命周期方法中的各种逻辑组合成单个函数调用。因此,最近,有开发者创建了一个称为Hookd的CLI模板工具,以将类组件立即转换为带有钩子的功能组件。

清理代码,理清思路

最后,使用React hooks似乎不仅仅是开发者“装x”的工具,它可以让开发人者清理自己的代码,进而理清自己的思路。所以不应低估简洁和声明性代码提高的可读性,开发效率和模块化且可维护的应用程序的重要性。在AI代替人类编程之前,我们必须遵循这些可以让打击都清楚理解的约束,这样才有助于项目壮大。