前言
Hook是React 16.8的新特性,可以让我们在不编写class的情况下使用state以及其他的React特性。Hook也出了好一阵了,据说很香,所以打算在新项目中使用Hook,本文内容适宜出入门Hook的童鞋~
为什么用Hook
有三个原因:组件之间复用逻辑很难;复杂的组件变得难以理解;难以理解的class。
1、先说组件之间的复用逻辑,我们在两个组件中想要复用逻辑会怎么办呢?常用的两种方案是render props(放到父组件) 和 高阶组件。
但是这类方案需要重新组织你的组件结构,如果你在 React DevTools 中观察过 React 应用,你会发现由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。(来源:react官网)
自定义Hook可以帮我们解决这个问题,就像函数调用一样简单。
2、复杂的组件变得难以理解。这是因为我们的组件里不可避免的会包含一些副作用。 比如我们要在组件里获取数据、订阅、清除或者操作DOM。这些都是副作用。我们可能会在componentDidMount函数里做获取数据和订阅事件,这就导致了componentDidMount函数功能变得不单一,而与订阅相关的清除操作却要被放到componentWillUnmount里面去。
useEffect可以帮我们解决这个问题。
3、难以理解的class。类会有一些构造函数啊、this指向之类的问题,相比之下,函数肯定是更简单的。
常用Hook
useState
先来看一个🌰:
useState对应到class里面就是setState。 如图所示,我们使用useState这个Hook声明了一个state变量叫做count,useState只接受一个参数,即变量的初始值。
useState返回一个数组,数组的第一项为state变量,第二项为改变state变量的函数,等价于setState。
如果要声明多个state变量怎么办?
当然,你也可以把它合并成一个,像这样:const [sth, setSth] = useState({age: 42, fruit: 'banana',todos: [{text: '学习Hook'}]})
。
但是这时候要注意Hook里面的state是替换而不像class里面的setState是合并。
如果我们像上面这样,把三个变量定义到一起,那么setSth({age: 18})
,就会丢掉fruit 和todos,这两个变量就为空了。正确的写法是setSth({...sth, color: 'red'})
.
useEffect
先说一下函数副作用:指一个function做了与返回值无关的操作。对于react组件来说,就是与组件渲染无关的操作。 react组件常见的副作用:数据获取、订阅 清理操作、操作DOM。
我们可以把副作用放到useEffect中,一个函数组件里面可以写多个useEffecct,这样就可以保持函数功能单一性。
useEffect可以在return函数里面执行清理操作,把订阅和取消操作放到一个函数里。
- useEffect 每次渲染都会执行
- 清理操作是可选的
- 每次渲染都会先清理再订阅
- 多个副作用应该拆分成多个useEffect
- 优化操作:如何跳过useEffecct(如下图)
如果想要useEffect只执行一次,第二个参数传空数组即可。等价于componentDidMount。
自定义Hook
自定义Hook是我们自己写的Hook,命名以use开头,采用驼峰命名的函数,内部可以调用其他Hook。
我们可以把公用的逻辑封装到自定义函数里面。像这样:
其他模块有用到直接像调用函数一样const isOnline = useFriendStatus(id);
即可。
自定义Hook抽离公用逻辑相比render props 和高阶组件,不再嵌套组件,耦合性更低。
useMemo
useMemo返回一个memoized值,只有在某个依赖项改变时,才重新计算。这个函数内部只执行与渲染相关的操作 : 返回一个react组件或是纯计算。(可以理解成在render里面可以执行的操作)
使用方法如下:只有当a或b改变时,函数才会重新计算返回值。
useMemo是帮我们做优化的,我们之前的render函数里面的一些纯计算,每次渲染的时候都会再次计算。
Memo
Memo等价于PureComponent,它并不是一个Hook,(你应该也发现了,它不是以use开头命名的)。
Memo的使用非常简单:export default memo(MemoDemo)
我们在最后export
的时候再用memo包一层就可以了,这样写等价于PureComponent。
memo 提供的第二个参数,可以让我们手动控制是否更新。
useRef
useRef可以用来获取子组件实例,但子组件必须是类组件。
那么父组件调用子组件的函数怎么实现呢?useImperativeHandle
可以帮我们实现。
以上demo的git地址:hook-demo。
Hook的使用规则
- 只在最顶层使用Hook (不能在循环、条件或嵌套函数里面使用)
- 只在React函数中调用Hook
1)React的函数组件 2)自定义Hook
我们可以使用eslint-plugin-react-hooks
这个eslint插件来帮我们做校验。
他是如何校验的?
- Hook调用在大驼峰命名的函数里
- 以use 开头,紧跟一个大写字母的函数(所以自定义Hook命名必须遵守规范)
是否要拥抱Hook
优点
- 解决了class 存在的问题(参见文章第一部分:为什么用Hook)
- 容易上手,100% 向后兼容
- react-redux,react-router 都 已经支持了Hook
缺点
- 早期阶段,一些第三方的库可能还无法兼容
- 一些不常用的生命周期如getSnapshotBeforeUpdate,getDerivedStateFromError componentDidCatch 的Hook写法还没有实现
最后
以上,如有错漏,恳请指正!
作者:小夭yao爱吃糖糖糖
链接:https://juejin.im/post/5e809c316fb9a03c4d40e65e
看完两件小事
如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:
- 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
- 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程