解读useState第二个参数的"第二个参数"
作者:Syn3ugar
场景引入
在学习react的过程中,为了希望能使用hook养成写函数式组件的习惯,我在完成日常作业的过程中刻意的使用hook,但也发现了几个两类组件里需要注意的问题。
先上一个需求场景:
当我们在类式组件中连续调用setState时,因为setState方法是异步的,所以即使执行到第三个setState方法时,count仍为初始值0,所以其实执行了三次setState,分别将count改为了1、2和3,但是因为最后一个方法覆盖了之前的state,所以count为3。
类式组件
参数传递回调函数
在类式组件中,如果我们希望使我们需要的三个setState方法都起我们想到的作用,最先想到的就是我们选择setState中参数的另一种传递方式——回调函数:
我们可以在此回调函数中收集到一个保存之前一次state的参数,这样虽然setState方法仍然是异步执行,但是当其执行的时候每次都能取到上一次的state并在此基础上做更新,可以达到我们想要的效果。
setState完成后执行的回调函数
另一种方式是采用setState的第二个参数——state改变后的回调函数:
这里要注意细节是不能提前解构赋值,这样只会取到第一次解构出来的初始值0。
但是这样的写法无疑是很难看的,写的多了就会形成回调地狱,使得代码可读性下降。
参数传递回调函数_promise版
为了解决回调地狱,很自然而然就想到使用promise做封装:
将按钮的回调函数变成异步函数,并把每一个setState方法用promise封装(因为await关键字只能等待promise),然后我们在state更改成功的回调里使用resolve放行,以此来模拟一个同步的场景。
函数式组件
说完了类式组件,我们调过头来使用hook来对我们的需求进行重构。
首先是使用useState的hook对组件进行重构,当然仅仅做改写肯定是没办法达到我们的需求的。
参数传递回调函数
于是立马使用useState的第二个参数,也就是操作数组的方法,将其写为一个回调函数,目的和类式组件一致,是为了拿到前一次的state:
那我们仍然很自然的就想复刻类式组件中setState的第二个参数——也就是传递一个state成功修改的回调函数的方法,这样我们的分享也可以到此结束了。
利用useEffect监听count的变化
可惜useState中提供的修改state的方法并没有提供这个回调函数,意味着useState的第二个参数并没有这么个“第二个参数”。
我们只好引入第二个hook:useEffect。
useEffect和类式组件中的生命周期有关系但并非完全有关系,我们时常通过监听一个空数组来模拟componnetDidMount生命周期,这里我们直接对count数据进行监听(类似vue中的watcher方法)。
当我们监控到了count的改变后重新调用按钮点击回调事件,但是这也容易造成死循环:回调函数改变数据,数据修改再次调用回调。
所以我们要设置好跳出条件,我们为了实现+1+2+3的方法甚至不惜再使用一个useState,其实写到这里我们已经觉得就实现这个需求而言已经不是一个很好的方法了。
那我们是不是可以再用promise进行包装呢?
答案是可以!
async_await
这一块其实是绕了一些弯子的,我们用promise进行一层包装后,发现没有第二个参数供我们调用resolve,于是我们想到把resolve抛出,当监听到count的变化的时候再执行resolve放行,同时把监听到的count作为参数传给下一个then方法,这个then方法再次返回一个新的promise并抛出一个新的resolve,当然我们最后也可以打印一下函数看看我们绑定在函数上的这两个resolve方法。
当然还是不建议这么去做的,很明显我们这种思路更多是为了强行去利用同步思想,在此作为一种思路扩展。
总结
此处再提出一些思路供大家思考:
1、能否用setTimeout等方法包裹一下setState执行顺序呢?本质思路其实是异步任务的执行顺序层次的思考了。
2、能否手写一个带回调函数的useState呢?可以借助useRef。
这些仅为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。