博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React Hooks 的钩子够用吗?
阅读量:4084 次
发布时间:2019-05-25

本文共 5305 字,大约阅读时间需要 17 分钟。

68747470733a2f2f7373312e62647374617469632e636f6d2f37306346765853685f5131596e78476b706f574b314846366868792f69742f753d323336393934373531392c393134343033373226666d3d31352667703d302e6a7067.jpg

阅读此文前假设你对 React 的新特性 Hooks 有一定的了解,并能进行最基本的使用。如果还不清楚 React Hooks 是什么,也没关系,建议读完本文后再去阅读下官方关于 React Hooks 的文档

今天,我们要来聊一聊 React Hooks 官方当前提供的 useXXX 到底够不够用,是否能满足我们日常的开发需求。

先说说最重要的 State

: 喂喂,useState 这么核心的 API,不就是专门用于处理 State 的吗?

是的,一点都没错。用过 useState 的同学应该都知道,原来的 this.setState,现在可以用 useState 的返回值中的方法 setXXX 代替,如下所示:

const [ count, setCount ] = useState(0);

我们来简单对比一下 class 和 Hooks 写法的差别:

# get stateclass: this.state.countHooks: count# set stateclass: this.setState({count: 1})Hooks: setCount(1)
: 这个太简单了,了解过 React Hooks 的人都懂啊。所以你就是要给我讲这个?

当然,不是啦。我问你,有没有发现少了些什么呢?原来的 this.setState(),是不是有第二个参数?这第二个参数是不是一个callback方法?这个方法是不是会在状态成功更新后调用呢?

: 你这么说好像是哦,但callback我没怎么用,你能说下使用的场景吗?

当然,信手拈来。

联动选择下拉框的场景,比如城市选择器。当选择了省份后,就需要去获取对应的城市列表,这个 moment 就会用到了。话不多说,先来看下 setState 的例子:

class App extends React.Component {  constructor() {    this.state = {      pId: 0    };  }  onProvinceChanged(val) {    this.setState({ pId: val }, () => {      fetchCities(this.state.pId);    });  }    ...}

想想如果没有callback,我们就比较难准确地在状态更新后进行一些其它的操作了

: 所以 React Hooks 对此束手无策?我不信,我不信,我不信。

当然,不是啦。可以用 useEffect 来实现这个需求。我们来看下使用 Hooks 方式如何实现上面的功能

function App() {  const [pId, setPId] = useState(0);  useEffect(    function() {      fetchCities(pId);    },    [pId]  );  function onProvinceChanged(val) {    setPId(val);  }}
: 那就是能实现咯,所以你接下来到底想说什么呢?

你仔细看下上面的代码,是不是很像事件的模式,一个在监听事件(useEffect 在监听 pId 的变化,然后执行方法)一个在触发事件(setPId)。

事件模式可以起到解耦代码的作用,但同时意味着代码很松散,一方只负责触发事件,而不用去关心接下来会发生什么。但我们这里的需求很明确,我选择了省份,接下来肯定是要加载城市数据,这两步的逻辑是有关联的,有先后顺序的,当然希望挨得越紧越好,这样代码比较有条理性,阅读起来比较顺畅,容易理解。不信的话,你就认真对比下上面的两个例子仔细推敲一下。

反正我是觉得还是得用回 callback 的方式,功能实现了固然重要,但代码的可读性,可维护性也很重要。

: 但你不是说 useState 没提供 callback 吗?那现在能怎么做?

当然,官方目前确实没有提供,但我们可以通过自定义 Hooks 的方式来实现。接下来我们会使用到第三方开源库  来满足我们的需求

把上面的例子用 nice-hooks 提供的方法重新写一遍,直接上代码,非常简单:

import { useStateCB } from 'nice-hooks';function App() {  const [pId, setPId] = useStateCB(0);  function onProvinceChanged(val) {    setPId(val, newPID => {      fetchCities(newPID);    });  }}

你看,callback 的回归,让 Hooks 在处理 state 方面至少保持与 this.setState 同等水平,不至于被吐槽落后了。

好了,Hooks 关于 state 的处理的介绍就告一段落了。

-------- ☕ 建议休息片刻,眺望远方 --------

接下来聊聊 Life Cycle 生命周期

我们都知道,每个组件都有它从生到死的生命周期。在 React 中,用的比较多的是:componentDidMountcomponentDidUpdatecomponentWillUnmount

: React Hooks 也应该有与之对应的 useXXX 方法吧?

我也觉得是,但现实是我们并没有发现官方有提供与之对应的 useXXX 方法。我知道你想说什么,放心,依然可以用官方当前提供的 API 来实现这些生命周期。接下来我们就一个一个来讲解。

  • componentDidMount

该生命周期方法会在组件挂载到DOM树后执行,我们可以利用 useEffect 来达到这个目的,怎么弄呢?看下例子吧

useEffect(() => {  console.log('Do something, such as fetching data');    }, [])

传个空数组,代表依赖项不变,所以只会执行一次,所以在组件第一次渲染完毕后执行一次,就相当于 componentDidMount 了

  • componentWillUnmout

在组件即将销毁时会执行该周期函数,那么对应的,我们依然可以利用 useEffect 来达到这个目的,看下例子:

useEffect(() => {  console.log('Do something, such as fetching data');  return function() {      console.log('Do something before destroyed')  }}, [])

因为销毁动作在整个生命周期中也只会执行一次,所以我们可以在第一个例子的基础上增加一个返回函数,该函数会在组件销毁的时候执行

  • componentDidUpdate

每当组件的 props,state 变化时,就会执行该周期函数,依然可以使用 useEffect 来达到该目的

useEffect(() => {  console.log('Do something when props / state changes')  })

这里并没有提供依赖值,所以在每次渲染后都会执行,类似于 componentDidUpdate。但这里有点小问题,就是在初始化时,这里也会执行,即这里会包括初始化,需要额外写些代码来支持,这里就不展开了。

我们还可以使用 useEffect 来达到 watch 某个 state 或 props 变化的目的。所以你会发现,生命周期被混在了一堆 useEffect 代码中,不是那么的直接了当。

虽然 useEffect 可以实现各种生命周期方法,但依然还是那个问题,代码的可读性和可维护性很重要。我们仍然可以用  ,使用它的 useLifeCycle 方法来让生命周期方法回归,用法非常简单,代码一目了然,就不啰嗦了,免得有凑字数嫌疑

useLifeCycle({  didMount() {    console.log('Do something, such as fetching data');  },  didUpdate() {    console.log('Do something when props / state changes')     },  willUnmount() {    console.log('Do something before destroyed')    }});

另外,class 组件写法的生命周期方法有个小缺陷,就是当需要在注销时销毁初始化时声明的一些东西,如事件监听,如定时器,注册和销毁的逻辑被强行拆开,容易疏忽而导致Bug,所以 useLifeCycle 提供了一个 didMountAndWillUnmount 配置来写成对的逻辑,如下

useLifeCycle({    didMountAndUnmount: [      {          didMount() {              console.log('register foo event)          },          willUnmount() {              console.log('unregister foo event)          }      },      {          didMount() {              console.log('register bar event)          },          willUnmount() {              console.log('unregister bar event)          }      }    ]})

那么推荐的做法就是需要写成对逻辑的写在 didMountAndWillUnmount,而不需要作销毁处理的初始化操作就写在 didMount,willUnmount这里就写一些没有成对逻辑的销毁操作

-------- ☕ 建议休息片刻,听听音乐 --------

最后谈谈 Instance Variables 实例变量

在使用 Hooks 写组件时,因为现在是纯函数组件,所以没法像 class 一样声明实例变量,以下使用变量是会有问题的

function comp() {    let name = 'daniel';}
: 看着挺正常的啊,函数内声明变量再正常不过的了。有啥问题呢?

你可能会在某个地方修改了 name 的值,并希望使用 name 变量时它的值是最后修改的值。可惜事与愿违,因为每次组件重新渲染时,渲染函数都会重新执行,那该变量就会被重新初始化。

: 好像是哦,现在就只剩下一个 render 函数,每次都会重新执行,那昨办呢?

我们可以利用 useRef 这个官方 hook,它的 current 属性会一直保持最后的值,如下:

function comp() {  const nameRef = useRef('daniel');  function someFn() {    // get    let name = nameRef.current;    // set    nameRef.current = 'sarah';  }}

一旦我们修改了 current 属性的值,那么下次重新渲染时,该 current 值会保持最后修改的值,即达到了实例变量的效果。

: 按照上面的套路,你肯定会说代码可读性不太好之类的

哎呀,台词都被你抢了。但我仍然还是要说,虽然这样利用 useRef 是达到了目的,但代码看起来总是不那么友好。

这里依然建议使用 ,它的 useInstanceVar hook,用法与 useState 一样,差别就是 setXXX 的时候只是改变实例变量的值而已,并不会导致重新渲染,示例如下:

function comp() {  const [nameVar, setNameVar] = useInstanceVar('daniel');  function someFn() {    // get    nameVar;    // set    setNameVar('sarah');  }}

建议在声明变量名时,使用 Var 作为后缀以区分开 state,如 [ xxxVar, setXXXVar ]

====== 华丽丽的结束分割线 ======

以上都是使用  这个第三方开源库来使 React Hooks 更好用。

如果你有好的建议,请多给这个开源项目提提issues;

如果觉得对你有用,那还请给这个开源项目加个星呗。

感谢阅读!

转载地址:http://vsqni.baihongyu.com/

你可能感兴趣的文章
删除docker容器和镜像的命令
查看>>
VINS-Fusion Intel® RealSense™ Depth Camera D435i
查看>>
使用Realsense D435i运行VINS-Fusion并建图
查看>>
gazebo似乎就是在装ROS的时候一起装了,装ROS的时候选择的是ros-melodic-desktop-full的话。
查看>>
React + TypeScript 实现泛型组件
查看>>
TypeScript 完全手册
查看>>
React Native之原理浅析
查看>>
Git操作清单
查看>>
基础算法
查看>>
前端面试
查看>>
React Hooks 异步操作踩坑记
查看>>
聊聊编码那些事,顺带实现base64
查看>>
TypeScript for React (Native) 进阶
查看>>
React 和 ReactNative 的渲染机制/ ReactNative 与原生之间的通信 / 如何自定义封装原生组件/RN中的多线程
查看>>
JavaScript实现DOM树的深度优先遍历和广度优先遍历
查看>>
webpack4 中的 React 全家桶配置指南,实战!
查看>>
react 设置代理(proxy) 实现跨域请求
查看>>
通过试题理解JavaScript
查看>>
webpack的面试题总结
查看>>
实践这一次,彻底搞懂浏览器缓存机制
查看>>