拥抱React Hook

463 阅读5分钟

前言

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插件来帮我们做校验。

他是如何校验的?

  1. Hook调用在大驼峰命名的函数里
  2. 以use 开头,紧跟一个大写字母的函数(所以自定义Hook命名必须遵守规范)

是否要拥抱Hook

优点

  • 解决了class 存在的问题(参见文章第一部分:为什么用Hook)
  • 容易上手,100% 向后兼容
  • react-redux,react-router 都 已经支持了Hook

缺点

  • 早期阶段,一些第三方的库可能还无法兼容
  • 一些不常用的生命周期如getSnapshotBeforeUpdate,getDerivedStateFromError componentDidCatch 的Hook写法还没有实现

最后

以上,如有错漏,恳请指正!