写在前面
在2018年React Conf大会上,React团队讲解了目前大家使用React开发过程存在的一些问题并推出了一个令人激动的概念:React Hooks,并介绍hooks如何解决这些问题。刚好我所在的团队前端使用的是react+ts+node这套技术栈,前段时间将react版本升级到了16.8,在这个过程中使用到了hooks的功能。在这里写下自己的感受和见解。
本文将从以下几部分进行总结:
- React Hooks之前还存在哪些痛点?
- React Hooks带来的代码模式改变
- 总结
- 扩展阅读
React Hooks之前还存在哪些痛点?
1.组件间相似的逻辑复用难
在react hooks问世之前,react能提供给组件间复用逻辑采用的是mixin,但这种方式弊端非常多,所以大家基本都是用render props(渲染属性)和HOC(高阶组件)来复用组件间的逻辑。但这两种方式的缺点也是非常明显的,很容易产生「wrapper hell」,即组件很容易臃肿,增加了debugger的成本。
2.生命周期的问题
在稍微复杂的业务逻辑当中,生命周期是绕不过去的问题。有很多场景下在componentDidmount处理的逻辑不得不又在componentDidUpdate重新处理一次,业务被分散在各个不同的生命周期函数中。
而Hooks的出现,从面向生命周期编程转变为面向业务逻辑编程
3.对人以及机器都不友好的class
虽然Hooks之前可以编写纯函数组件,但它只能是无状态的。非常常见的情况是你编写了一个纯函数组件,但过一段时间你发现得加上一个状态,这时候不得不将纯函数组件改为类组件。这时候得处理this的问题,而且编译器对class也是不友好的。
React Hooks带来的代码模式改变
import { useState, useEffect } from 'react' function Example(props) { // 声明一个新的状态变量"count" const [count, setCount] = useState(0); useEffect(() => { subscribe(props.number, setCount) return () => { unsubscribe(props.number) } }) return{count}}复制代码
useState会接受一个初始值,然后会返回一个[状态,状态修改器]的二元组。每次重新渲染时,整个函数会重新执行,但是useState会记住上次的值。而且react是根据useState的调用顺序来记得状态归属的:
const Example = () => { const [size, setSize] = useState({ width: 100, height: 100 }); const [count, setCount] = useState(0); }复制代码
每一次 Example 被渲染,都是第一次 useState 调用获得 size 和 setSize,第二次 useState 调用获得 count 和 setCount。
useEffect是用来处理副作用的,相当于以前的componentDidMount和componentDidUpdate。它可以返回一个函数,用来在组件卸载前调用。所以useEffect聚合了componentDidMount,componentDidUpdate和componentWillUnmount的操作。如果props.number没有改变,我们是不希望在useEffect订阅/取消订阅里重新执行的,为了实现这个操作,只需要传入一个参数即可。
useEffect(() => { subscribe(props.number, setCount) return () => { unsubscribe(props.number) } }, [props.number])复制代码
它是一个数组,只要这个数组中的每一个值都不发生变化,则useEffect不需要重新执行。
在将react升级之后,生命周期函数迁移到函数组件可以按照以下方式来操作:
- constructor: 使用 useState 来初始化 state。
- getDerivedStateFromProps: 函数组件本身可以直接操作。
- shouldComponentUpdate: 使用 React.memo。
- render: 函数组件本身。
- componentDidMount, componentDidUpdate, componentWillUnmount: 使用 useEffect 实现。
在react hooks开发的过程中,共享代码会采取函数组件的形式。约定使用useXXX开头,想重用这些代码,在函数式组件通过调用useXXX即可。
例如我们可以写这样一个useMountLog,在组件mount的时候打印出一条日志:
const useMountLog = (name) => { useEffect(() => { console.log(`${name} mounted`); });}复制代码
这样一来,所有的函数式组件中都可以通过调用useMountLog来使用这个功能:
const Example = () => { useMountLog('Example')}复制代码
再比如可以通过写useWindowWidth这样一个自定义hook来反映当前窗口的宽度
function useWindowWidth() { const [width, setWidth] = useState(window.innerWidth); useEffect(() => { const handleResize = () => setWidth(window.innerWidth); window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); }; }); return width;}复制代码
这样就可以在其他函数式组件中使用:
function MyResponsiveComponent() { const width = useWindowWidth(); return (Window width is {width}
);}复制代码
在上边的例子可以看到,react hooks大大地减少我们的代码量。而hooks只能在函数式组件中使用,所以可以预测虽然现在react仍然支持类组件,但以后类组件会慢慢得消亡。
总结
React Hooks的出现,将大大地减少react的代码量,解决原来使用react开发遇到的一些问题,也给开发带来了一些变化。包括:
- react hooks将改变组件间重用代码的方式。使用自定义 hooks,没有 mixin 带来的混乱,没有 HOC 带来的层级深渊。
- 从面向生命周期编程到面向业务逻辑编程
- 不再需要class,不再需要关注this等问题
最后,引用 Dan Abramov 在React Conf 2018 上的演讲词结束本文:
“……我曾经疑惑,React 的 Logo 为什么是一个原子?后来我想到了这个解释。我们知道物质由原子组成,是原子的特性决定了物质的外观和行为。就像 React,你可以把用户视图拆成独立的组件,再像原子一样自由组合起来,是组件的特性决定了用户视图的行为。科学家们曾一度认为原子是不可分割的最小单位,直到发现了电子,一种原子内部更小的粒子。事实上,是电子的特征影响了原子的性质。我认为 hooks 就好比电子,与其说它是一个新特性,不如说是已知的 React 特性(state,context,生命周期)的更直接的展现形式,而这四年来我们却一直对它视而不见。
如果盯着 React 的 logo 看的话,你会发现 hooks 其实一直都在。”