原链接# Where did Hooks come from?:
Hooks到底是什么?为什么React会采用Hooks?Hooks比类组件更好吗?
Hooks用于在组件之间共享公共逻辑。需要明确的是,这里的“逻辑”并不是指组件的UI部分(JSX)。我们讨论的是JSX之前组件中的所有内容。在类组件中,我们会说它在生命周期方法和自定义方法中。
在某种程度上,Hooks的故事与React及其之前用于共享代码的api的故事密切相关。所以请大家耐心听我讲,让我们回到起点。
第一个 React API: 2013
简而言之:React开发者不喜欢mixins,这是第一个用于共享逻辑的API。
最初,React有一种在组件之间共享公共逻辑的方法,称为mixin。
最初,React有一种在组件之间共享公共逻辑的方法,称为mixin。这是React的早期,JavaScript还没有类。这些伪类的组件允许“混合”共享逻辑。当时,mixin被认为是一些反模式开始在社区中传播的根本原因。因此,当2016年React有了真正的类,mixin的API消失时,大多数React开发者都欢呼雀跃。
类组件:2016
简而言之:类不能共享逻辑,所以社区发明了一些模式来共享逻辑。
JavaScript在ES2015 (ES6)中有了类之后,React很快就有了你今天仍然可以使用的类组件。但如果你是React的新手,你可能会想,为什么人们普遍认为在React中应该完全避免使用类组件?
主要原因是类组件很难共享逻辑。当我们失去mixin时,我们也失去了共享代码的基本方式。你可以通过创建一个新的组件来共享JSX来共享/重用UI,但是没有内置的方法来共享生命周期方法,例如componentDidMount、componentDidUpdate和componentWillUnmount。
这些方法尤其适用于管理组件的副作用。因此,如果您要编写带有某种副作用的ComponentOne,则必须将该逻辑复制到ComponentTwo,这样你的逻辑就不会以编写一次的方式进行抽象。
我们不能只做继承吗?
class ComponentOne extends SharableStuff{
// ...
}
class ComponentTwo extends SharableStuff {
// ...
}
不行,React不允许我们像这样编写从其他组件继承的组件。另外,即使React允许你这样做,你如何在ComponentOne中共享多个逻辑体呢?多重继承是不允许的,所以这将不起作用:
class ComponentOne extends SharableStuffA extends SharableStuffB {
// ...
}
我之所以提出这个问题,是因为这些是我在研讨会上遇到的常见问题,当我向拥有大型面向对象背景的团队讲授Hooks时。继承是他们的直觉告诉他们的类之间共享方法,所以我理解。
React类必须扩展其中一个React.Component或React.PureComponent。React本身没有api来共享代码。
这个社区很聪明。React开发人员创建了两种模式来有效地在组件之间共享代码,它们被称为高阶组件(Hoc)和Render Props。
HoC 和 Render Props
我不打算在这篇文章中深入探讨这些模式,但在我们使用这些模式一段时间并在Twitter上痛击它们大约两年之后,HoC模式显然是有问题的和令人困惑的。Render Props模式修复了HoC的主要问题,但也令人困惑,有一些独特的问题。
无状态函数组件
与此同时,React团队宣布了一种只用函数而不用类来制作组件的新方法。当时的主要想法是有一个只接受props并可以返回JSX的组件。没有state或能力使用React API类似于类生命周期方法。
我们称它们为无状态函数组件,因为它们不能有state。
不久之后,React团队告诉我们不要这样称呼他们。我们应该称它们为函数组件,因为……他们有计划🤔
Hooks in 2018
我很幸运地参加了2018年底Hooks的发布大会。我还记得那种氛围和兴奋。这也有点让人害怕。这感觉就像我投入在成为React专家上的时间被重置了,我们都必须想出这个Hooks是什么的新想法。
本质上,Hooks只是一个函数,我们可以从函数组件中调用它。你可以使用内置的,并编写自己的:
- 内置组件Hooks:这些api,如useState(),使函数组件能够“钩入”React的所有特性。
- 自定义Hooks:这些只是我们编写的实现内置钩子的函数。自定义Hooks的一般思想是为任何想要使用它的组件创建可重用的逻辑。
举个例子,React有useState(),所以函数组件可以有自己的本地状态,类似于类组件有state。但是如果你刷新页面,所有的本地状态都会被重置(就像任何其他JS变量一样)。这样你就可以自己写一个useLocalStorageState(),就像useState()留存state到localStorage用于在刷新后再获取之前的值。
下面是另一个使用自定义钩子共享数据获取逻辑的示例。你不需要完全理解如何使用useState和useEffect,下面是另一个使用自定义钩子共享数据获取逻辑的示例。你不需要完全理解如何使用useState和useEffect,你只需要理解它们为我的组件做了一些逻辑,我想分享它。如果另一个组件也想根据productId获取products,则需要重新编写第2到5行的代码:
function BrowseProducts({ productId }) {
const [product, setProduct] = useState(null)//2
useEffect(() => {
fetchProduct(productId).then((product) => setProduct(product))
}, [productId])//5
// return <div>...</div>
}
下面是同样的逻辑移动到一个自定义Hook。现在任何组件都可以使用useFetchProduct钩子:
// Custom Hook
function useFetchProduct(productId) {
const [product, setProduct] = useState(null)
useEffect(() => {
fetchProduct(productId).then((product) => setProduct(product))
}, [productId])
return product
}
function BrowseProducts({ productId }) {
const product = useFetchProduct(productId)
// return <div>...</div>
}
这是一个过于简化的示例,上面的useEffect代码不完整。如果你想要一个数据获取自定义钩子,我可能会推荐React Query中的自定义钩子useQuery()。
今天,如果你愿意,你仍然可以用类组件。如果你觉得它们更容易使用,那完全取决于你。即使你不介意这些问题,也不觉得Hoc和Render Props令人困惑,你可能会很难与过去5年里基于React开发的同事合作。当Hooks被推荐为React的主要方式时,他们开始使用React。他们可能不知道类组件的“挂载和卸载”,如何处理他们奇怪的this范围问题,以及如何决定何时以及如何使用Hoc或Render Props。此外,React生态系统中的绝大多数第三方库已经不再使用Hoc和Render Props,而是采用了Hooks。所以你不能很容易地使用他们的工具,因为Hooks只与函数组件一起工作。
我的一些长期使用React的朋友还记得这些对话和权衡。但我注意到一件事(至少在Twitter上),历史正在重演。新一代React开发人员不知道这个背景故事,也不知道我们为什么要有Hooks。
我的一些长期使用React的朋友还记得这些对话和权衡。但我注意到一件事(至少在Twitter上),历史正在重演。新一代React开发人员不知道这个背景故事,也不知道我们为什么要有Hooks。我承认,Hooks的某些部分比类更难,比如我们可能需要内存化(useMemo和useCallback),但这是一种权衡。你可以使用带有HoC和Render Props的类(也不容易),或者使用具有轻松共享代码能力的Hooks,但需要理解内存的复杂性。