开场
![](https://pic4.zhimg.com/80/v2-9b176687646ccd63152d98d2b8b56fd3_720w.jpg)
大家好,我叫尽龙,来自体验技术部。社区名称叫 brickspert,砖家的意思。
自从 React 推出 React Hooks 后,就开始尝试使用 Hooks,并逐渐喜欢上了它。目前,几乎 100% 的组件都是使用 Hooks 开发。经过大半年的实践,在 Hooks 使用方面沉淀了一些经验,很高兴今天有机会能分享给大家。
![](https://pic3.zhimg.com/80/v2-6fa1d660558d73df4e79a7ea304bf712_720w.jpg)
在分享开始之前,我想了解下:"有多少同学目前已经在项目中大量使用 Hooks 了?"
嗯嗯,谢谢。看举手的同学,大概一半一半吧。没关系,听完今天的分享,我相信你一定有兴趣尝试下 Hooks 的。
React Hooks 是 react v16.8 的一个新特性,很佩服这么重磅的功能,在一个小版本中发布,说明 React 团队有足够的信心向上兼容。
Why Hooks?
![](https://pic4.zhimg.com/80/v2-46cc3929b920f967b41064a99beb67b3_720w.jpg)
为什么要放弃 Class,转用 Hooks 呢?在内部外部有很多争论,包括知乎也有类似提问。我们也不免俗套的要对比下 Class 和 Hooks 了。当然为了保证今天的分享效果,我肯定会偏向 Hooks 的(哈哈哈哈)。
![](https://pic3.zhimg.com/80/v2-2fd987342f118ea9f3a154842bbe1d16_720w.jpg)
Class 学习成本高
Class 学习成本很高。首当其冲的就是生命周期,多,太多了。不仅多,还会变!React v15 和 v16 就不一样。下面是我在网上随便找的一张图。
![](https://pic2.zhimg.com/80/v2-69a3b2dbe64a5575d46ff6541690c629_720w.jpg)
这个是 React v15 的生命周期,你都掌握了吗?你知道 v16 有什么变化吗?
之前无论你去哪里面试,基本都会有几个必问问题:
- 讲讲 React 生命周期?React v15 和 React v16 生命周期有啥变化?
- 如何优化 Class 组件?
shouldComponentUpdate
是做什么的?如何用?
- 一般在哪个生命周期发送网络请求?为什么?
- ......
生命周期最重要,但是有很高的学习成本,需要大量实践才能积累足够的经验。当然,这几个问题回答不好,百分之八十以上的几率会挂掉。
当然不止是生命周期, this
也是一个很大的问题。你有没有在组件写很多 bind
?或者所有的函数都用箭头函数定义?
this.someFunction = this.someFunction.bind(this);
// 或
someFunction = ()=>{}
为什么要这样写呢?如果不写会有什么问题?哎呦,又多了一个面试题,你会吗?
Hooks 学习成本低
对比 Class,Hooks 的学习成本可就太低了!掌握了 useState 和 useEffect,80% 的事情就搞定了。
![](https://pic3.zhimg.com/80/v2-8e1f61d3667805ce781292a3aa5b46ca_720w.png)
Class 业务逻辑分散
Class 业务逻辑分散,实现一个功能,我要写在不同的生命周期里面,不聚合~
比如,如果你有个定时器,你一定要在 componentWillUnMount
去卸载。
![](https://pic4.zhimg.com/80/v2-642bf5675e0dc09dede8a9a5baace3df_720w.jpg)
再比如,我们要写一个请求用户信息的组件,当userId
变化时,要重新发起请求。我们就要在两个生命中期中写请求的逻辑。
![](https://pic3.zhimg.com/80/v2-3114c0566665e5e6e1cb5eeed96327be_720w.jpg)
相信上面的逻辑,大家也是经常会写的吧。
奥奥,sorry,上面的 componentWillReceiveProps
已经被废弃了,我们应该用 componentDidUpdate
来代替。
"咦,这是为啥呢?好好的为什么要废弃,不让这么用了?"
又来一个面试题!你知道答案吗?
Hooks 业务逻辑聚合
而 Hooks 的业务逻辑就非常聚合了。上面的两个例子,改成 Hooks 你会写吗?
![](https://pic1.zhimg.com/80/v2-9e0275e88d554976456728d8c4d6afe4_720w.jpg)
![](https://pic2.zhimg.com/80/v2-920874635d69c21fc538861739c88eed_720w.jpg)
简直不要太简单!香啊!我可以提前下班了。
Class 逻辑复用困难
说到逻辑复用,很多同学会说 Class 的 Render Props 和 HOC(高阶组件)可以做逻辑复用!那我们看看 Class 的逻辑复用有多么的惨不忍睹。
首先我们看看 Render Props。
首先我们想复用监听 window size 变化的逻辑,开开心心的写了下面的代码。
![](https://pic1.zhimg.com/80/v2-0809c9f6527fe95aba11ac16791e388c_720w.jpg)
然后,我又想复用监听鼠标位置的逻辑,我只能这么写了。
![](https://pic4.zhimg.com/80/v2-7e23cbfbe0ad3996ec5340d2ea7b0333_720w.jpg)
到这里你应该看到了问题所在。这简直就是地狱!我不忍心复用其它逻辑了。
我们放过 Render Props,来看看 HOC 吧。
![](https://pic2.zhimg.com/80/v2-14d1f21906211ceee5eacf87b21d096d_720w.png)
上面的代码,我用了三个 HOC,分别是 redux
的 connect
, react-intl
的 injectIntl
,以及 AntD 的 Form.create()
。
这是一个非常常见的用法。如果你光看代码,大概已经懵圈了。"我是谁?我在哪?我要干什么?"
这会我仿佛听见 HOC 在说:"我不仅让你看不懂我,我还很容易出各种问题。"
是的,HOC 很容易出问题。大家都往组件的 props
上面挂属性,万一有个重名的,那就只能说一句"不好意思,GG思密达"!
Hooks 逻辑复用简单
Hooks 来了,它表示,我要一个打五个!Render Props 和 HOC 联合起来也被我秒杀!
![](https://pic3.zhimg.com/80/v2-0be6f8b8ca9a43ea4c1bbef941eb1ba6_720w.jpg)
Hooks 表示,来十个,来一百个我也能打。
Hooks 最强的能力就是逻辑复用了,这是我最最最爱的能力了。
Hooks 会产生很多闭包问题
是的,我也不偏袒 Hooks,由于 React Hooks 的机制,如果用法不正确,会导致各种奇怪的闭包问题。
那面对这个问题,怎么解呢?说实话,我也没有很好的解决办法。
但是,这可能也有好处。如果碰到想不明白的问题,那 99% 是由于闭包导致的,我们有很确定的方向去排查问题。
![](https://pic4.zhimg.com/80/v2-391b87c21c8b23495668db7977ff7c0f_720w.jpg)
记住这句话,你可以少走很多弯路。
Show Case
![](https://pic2.zhimg.com/80/v2-886e180a6a378fa0cfc8addf70079b25_720w.jpg)
当然,说再多,吹再好,也没多大用。我上面讲的 Class 和 Hooks 的优缺点,网上的也有很多人讲,大家也肯定都看过。
用程序员的交流方式,就是 Talk is cheap,Show me the code.
。
亮剑吧!
接下来,我会用一个例子,让你折服,拜倒在 Hooks 的石榴裙下。如果你不服,咱们单独撕~
网络请求组件实现
![](https://pic2.zhimg.com/80/v2-6b6d3b6f506b7e7448ad119ea7a44165_720w.jpg)
接下来,我们来实现一个最最最常见的组件。该组件接收 userId
,然后发起网络请求,获得用户信息。
说白了,就是最简单的发起网络请求的组件。我们先用 Class 来实现看看。
![](https://pic2.zhimg.com/80/v2-5572cb170ca889d800c70656e745e2ad_720w.jpg)
这段代码,是最简单的网络请求。
- 定义一个
username
状态。
componentDidMount
的时候发起网络请求。
- 网络请求结束,更新
username
。
美滋滋。但是少了点东西。网络请求,我们肯定要维护一个 loading
状态,保证用户体验比较好。
那我们加上吧。
![](https://pic1.zhimg.com/80/v2-9a56250f4dbd28e476fcd9e2505a5094_720w.jpg)
这张图,我们增加了 loading
状态,在网络请求发起前,置为 true,在网络请求结束后,置为 false。
美滋滋。但是还是少点东西。 userId
变化后,我要重新发起网络请求吧。
我们再加点代码吧。
![](https://pic4.zhimg.com/80/v2-986e284a9f0802c3bfc8cef6a075c52f_720w.jpg)
我们增加了对 userId
变化的监听,如果 userId
变化后,重新发起请求。
这次稳了吧?
不不不,还不够。如果 userId
连续变化了 5 次,发送了 5 个网络请求,我们要保证总是最后一次网络请求有效。也就是经常说的的"竞态处理"或者"时序控制"。
我加!加还不行吗!
![](https://pic3.zhimg.com/80/v2-65367bd5215d24cebe38bc57fe875676_720w.jpg)
其实到这里,有些同学已经懵了。"你说的时序控制,听着很有道理,但我平时都没处理过这个问题,我看下你怎么实现的。"
确实,时序控制不算一个简单的问题,很多新手都不会解决这个问题。
稳了!到这里你觉得稳了吧。
还是年轻啊,小伙子。
![](https://pic4.zhimg.com/80/v2-c45da7048d216ecc64a5cb81300db8c3_720w.png)
如果用上面的代码来玩,你可能会偶尔碰到上面的警告。这个警告是怎么造成的呢?我说一下你就明白了。下面四个步骤执行,必会报警告:
- 组件加载
- 发起网络请求
- 组件卸载
- 网络请求请求成功,触发
setState
看出问题了吗?组件已经卸载了,还去 setState
,造成了内存溢出。
怎么解决呢?
![](https://pic3.zhimg.com/80/v2-773c1fb061a4c4b816bfee3da6b8d026_720w.jpg)
在组件卸载的时候,放弃最后一次请求。
到这里为止,我们就完成了一个完美的网络请求。这次真结束了!
看下写了多少行代码。
![](https://pic2.zhimg.com/80/v2-690e58c0ef413e6fb2cc3766eec0234d_720w.jpg)
除去空格,我们写了 38 行代码。实话说,38 行代码我能忍,但是这些逻辑我忍不了!回想下我们处理了多少逻辑:
- 网络请求
- loading
userId
变化重新发起请求
- 竞态处理
- 组件卸载放弃网络请求
关键这些逻辑是没办法复用的!每个项目可能有数十上百个组件会发网络请求,我就要写几十,几百遍这样的逻辑。想想我都难受。
说实话,我在写项目的时候经常会偷懒。要不就不写 loading,要不就不管竞态,要不就不管最后的内存溢出警告。
你有没有和我一样呢?嘿嘿。
言归正传,接下来就邀请 Hooks 登场了。
![](https://pic4.zhimg.com/80/v2-d1471f1630479ef4e4413cb7117cd27f_720w.jpg)
三下五除二,我们用 Hooks 实现了刚才所有的逻辑。
![](https://pic3.zhimg.com/80/v2-6f716c768c4bb78fdd1ffe388252f826_720w.jpg)
17 行!代码量减少了 50% 以上。好像还行!
但是,别忘了,Hooks 最重要的能力就是逻辑复用!这些逻辑我们完全可以封装起来!我们把刚才的逻辑全部封装起来!
![](https://pic4.zhimg.com/80/v2-3a9717ad713abb115230c18846c0c067_720w.jpg)
useAsync 封装了刚才我们说的所有功能,一行代码完成了网络请求。
最后整个组件会长这样。
![](https://pic3.zhimg.com/80/v2-b6d02a32f2d2465c7fba8655f99fac92_720w.jpg)
哇!我自己都佩服自己!简直了!美呆了,帅毙了,感觉自己无敌了!提前完成工作,下班回家!
![](https://pic1.zhimg.com/80/v2-53011b7a234d1e2cf0bfb8b1d987e3e8_720w.jpg)
通过这个例子,我想证明一个论点:"使用 Hooks 封装逻辑的能力,可以极大提高开发效率"。
Umi Hooks
这时候你肯定要问,useAsync 在哪里?给我瞧瞧?
![](https://pic2.zhimg.com/80/v2-970f0e0f184df5292a42b0fc662a51bd_720w.jpg)
useAsync
useAsync 在这里,快来瞧,快来看啦!
useAsync 是 Umi Hooks 库的核心 Hooks 之一,Umi Hooks 提供了大量提炼自业务的 Hooks。一行代码真的可以实现很多功能!
![](https://pic4.zhimg.com/80/v2-1ee77bacb25ff1cc05a914ca1900d847_720w.jpg)
当然,useAsync 不止包含上面说的功能,还支持"手动触发"执行,还支持"轮询"!
只要简单的配置一个 pollingInterval
,就能轮询发送请求了。快去试试啦!
接下来我们会介绍几个更牛的 Hooks 给大家认识!
useAntdTable
![](https://pic3.zhimg.com/v2-26598969b93cc532d905ff7d19dd223a_b.jpg)
AntD 的 Table 组件,想必大家在项目中经常用到吧!除了刚才异步请求的所有逻辑外,你还得处理其它的逻辑。
![](https://pic3.zhimg.com/80/v2-4076e1aaac23c0978d8a8d4d529bb78a_720w.jpg)
比如维护 page
、 pageSize
、 sorter
、 filter
的状态,还得考虑搜索条件变化后,重置 page
到第一页。这些逻辑光想想就头疼了,别说写了。
现在一行代码就可以实现了!useAntdTable,封装了所有的逻辑,只要一行代码!如图上所示,你只要 ...tableProps
,就可以了。这也许就是幸福的味道吧~
useLoadMore
加载更多的场景,比如下面动图的场景,想必大家在工作中都写过。
![](https://pic3.zhimg.com/v2-26598969b93cc532d905ff7d19dd223a_b.jpg)
这样一个加载更多的场景,我们要维护多少状态?写多少行逻辑?本来我打算写个 Class 实现的例子贴出来的,但是我放弃了,因为太难了~~
随便想想要处理的逻辑:
- 第一次加载时候的
loading
- 加载更多时候的
loading
- 维护
page
和 pageSize
- 网络请求
- 是不是加载全了
- 搜索条件变化后,重置到第一页。
- .....
脑壳疼,真的脑壳疼。我会写,但是写起来真的好累。
还没完,一般产品同学还会要求,上拉加载更多......
![](https://pic1.zhimg.com/v2-e18267f9d606f4891eb0aaa2e800c160_b.jpg)
这时候我们还得监听滚动位置,如果快到底了,触发加载更多。脑壳更疼了!
![](https://pic3.zhimg.com/80/v2-13430c329659735d201edd4e90281d4a_720w.jpg)
Umi Hooks 听到了你的求救,派出 useLoadMore 来拯救你了。一行代码就可以实现所有的功能!一个小时变一分钟,又可以早点下班了。
useDynamicList
![](https://pic3.zhimg.com/80/v2-41741abe99dec98247971c55967dda4a_720w.jpg)
还有更好用的,比如 useDynamicList,下面的动态列表,一行代码搞定。
![](https://pic3.zhimg.com/v2-92ebb2d74f6d6bd0fbdff16ce1e2c262_b.jpg)
useBoolean
不仅是上面讲到的各种复杂逻辑可以封装。简单的逻辑封装起来也是极其好用的,比如 Boolean 值的管理。
我们一般控制 Modal,Popover 等显示隐藏的时候,都要维护一个 visible
状态。大概会是这样。
![](https://pic3.zhimg.com/80/v2-3bcf90c8885cc514bef0dc0a88f4df36_720w.jpg)
这样的逻辑,你写过多少遍?没有几千也有几百吧!
![](https://pic4.zhimg.com/80/v2-48afae41d2c16019c997b3a664e45737_720w.jpg)
以后你就可以用 useBoolean 咯!
More
![](https://pic3.zhimg.com/80/v2-732c501d199486c9fb0dc5866078d9ae_720w.jpg)
不仅是上面讲到的这些,我们还有很多很多的 Hooks。
比如 useSearch,就封装了通常异步搜索场景的逻辑,比如 debounce。
比如 useVirtualList,就封装了虚拟列表的逻辑。
![](https://pic4.zhimg.com/80/v2-100511540516eea552c743290220fcf7_720w.jpg)
比如 useMouse,封装了监听鼠标位置的逻辑。
比如 useKeyPress,封装了监听键盘按键的逻辑。
![](https://pic1.zhimg.com/80/v2-7bd8fe6739c7287f3ab42f6f0a8b8288_720w.jpg)
30+ Hooks 供您选择,并且我们仍然处于婴儿期,快速发展中。我们的愿景就是:封装别人的逻辑,让别人无逻辑可写。
未来规划
![](https://pic3.zhimg.com/80/v2-63da32f568e040850200947c2fac70c6_720w.jpg)
更多的 Hooks 开发
如上面所述,我们现在还处于婴儿期,需要不断汲取能量,更多的 Hooks 正在路上!要实现"让别人无逻辑可写"的目标,还需继续奋斗。
更强大的 useRequest
![](https://pic3.zhimg.com/80/v2-e982d92ccc1922fc575a6e933f1caf3a_720w.jpg)
大家应该都听过 useSWR 吧?是 zeit 公司开发的一个专门做网络请求的 Hooks,提供了很多新颖的思路,给了我们非常大的启发,github star 就像坐火箭一样。但在实际项目使用中,还是会有很多地方不符合蚂蚁内部的体系。
但是它给我们非常大的启发,基于 swr 的思路,我们可以实现更强大的 useRequest!图上的能力,我们都要!
useRequest 目前已经处于内测期了,下个版本将会与大家见面!我们的目标是:所有的网络请求,只用 useRequest 就够了!
Hooks 生态
目前社区上 Hooks 相关的基础教程、进阶教程、原理深入、常见问题等文档都比较分散,我们准备向 Hooks 生态发展,提供各式各样的文章。以后学习 Hooks,使用 Hooks,找 Umi Hooks 就对了。
当然,生态方面目前正在规划中,预计年后启动。
总结
![](https://pic1.zhimg.com/80/v2-ed81375215538a7db35f74c1d2b68038_720w.jpg)
Umi Hooks,你值得拥有。
我们目前处于发展阶段,欢迎大家一起共建。
你可以提 idea,我们负责实现。
你可以提 issue,我们负责改 bug。
你可以提 PR,将你封装的 Hooks 分享给大家,让更多人收益。
❤️期待您的参与。