Umi Hooks - 助力拥抱 React Hooks

3,399 阅读12分钟

这是蚂蚁金服内部技术分享的文字稿,本次分享主要介绍了为什么要用 Hooks?以及如何使用 Umi Hooks 提效?

Umi Hooks github.com/umijs/hooks

开场

image-20200116191741370

大家好,我叫尽龙,来自体验技术部。社区名称叫 brickspert,砖家的意思。

自从 React 推出 React Hooks 后,就开始尝试使用 Hooks,并逐渐喜欢上了它。目前,几乎 100% 的组件都是使用 Hooks 开发。经过大半年的实践,在 Hooks 使用方面沉淀了一些经验,很高兴今天有机会能分享给大家。

image-20200116191759286

在分享开始之前,我想了解下:“有多少同学目前已经在项目中大量使用 Hooks 了?”

嗯嗯,谢谢。看举手的同学,大概一半一半吧。没关系,听完今天的分享,我相信你一定有兴趣尝试下 Hooks 的。

React Hooks 是 react v16.8 的一个新特性,很佩服这么重磅的功能,在一个小版本中发布,说明 React 团队有足够的信心向上兼容。

Why Hooks?

image-20200116191817331

为什么要放弃 Class,转用 Hooks 呢?在内部外部有很多争论,包括知乎也有类似提问。我们也不免俗套的要对比下 Class 和 Hooks 了。当然为了保证今天的分享效果,我肯定会偏向 Hooks 的(哈哈哈哈)。

image-20200116192324872

Class 学习成本高

Class 学习成本很高。首当其中的就是生命周期,多,太多了。不仅多,还会变!React v15 和 v16 就不一样。下面是我在网上随便找的一张图。

image-a5b927b35025

这个是 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% 的事情就搞定了。

image-7cbf7879e7cf

Class 业务逻辑分散

Class 业务逻辑分散,实现一个功能,我要写在不同的生命周期里面,不聚合~

比如,如果你有个定时器,你一定要在 componentWillUnMount 去卸载。

image-67b7d915f6af

再比如,我们要写一个请求用户信息的组件,当userId 变化时,要重新发起请求。我们就要在两个生命中期中写请求的逻辑。

image-4059e72aa129

相信上面的逻辑,大家也是经常会写的吧。

奥奥,sorry,上面的 componentWillReceiveProps 已经被废弃了,我们应该用 componentDidUpdate 来代替。

“咦,这是为啥呢?好好的为什么要废弃,不让这么用了?”

又来一个面试题!你知道答案吗?

Hooks 业务逻辑聚合

而 Hooks 的业务逻辑就非常聚合了。上面的两个例子,改成 Hooks 你会写吗?

image-3780dc60b735

image-88e9ba8a7add

简直不要太简单!香啊!我可以提前下班了。

Class 逻辑复用困难

说到逻辑复用,很多同学会说 Class 的 Render Props 和 HOC(高阶组件)可以做逻辑复用!那我们看看 Class 的逻辑复用有多么的惨不忍睹。

首先我们看看 Render Props。

首先我们想复用监听 window size 变化的逻辑,开开心心的写了下面的代码。

image-f9273eefa2ef

然后,我又想复用监听鼠标位置的逻辑,我只能这么写了。

image-d60b6492b570

到这里你应该看到了问题所在。这简直就是地狱!我不忍心复用其它逻辑了。

我们放过 Render Props,来看看 HOC 吧。

如果你要问什么是 HOC,那我不得不推荐我的另外一篇文章《助你完全理解React高阶组件(Higher-Order Components)》。

哪怕你不知道 HOC 是啥,你也一定用过。比如 redux 的 connect。

image-20200116200932301

上面的代码,我用了三个 HOC,分别是 redux 的 connect,react-intl 的 injectIntl,以及 AntD 的 Form.create()。

这是一个非常常见的用法。如果你光看代码,大概已经懵圈了。“我是谁?我在哪?我要干什么?”

这会我仿佛听见 HOC 在说:“我不仅让你看不懂我,我还很容易出各种问题。”

是的,HOC 很容易出问题。大家都往组件的 props 上面挂属性,万一有个重名的,那就只能说一句“不好意思,GG思密达”!

Hooks 逻辑复用简单

Hooks 来了,它表示,我要一个打五个!Render Props 和 HOC 联合起来也被我秒杀!

image-5a6f5d648ca9

Hooks 表示,来十个,来一百个我也能打。

Hooks 最强的能力就是逻辑复用了,这是我最最最爱的能力了。

Hooks 会产生很多闭包问题

是的,我也不偏袒 Hooks,由于 React Hooks 的机制,如果用法不正确,会导致各种奇怪的闭包问题。

如果你要问 React Hooks 的机制是什么的话,我又要给你推荐一篇我之前写的文章了:《React Hooks 原理》。

那面对这个问题,怎么解呢?说实话,我也没有很好的解决办法。

但是,这可能也有好处。如果碰到想不明白的问题,那 99% 是由于闭包导致的,我们有很确定的方向去排查问题。

image-4636b47be14f

记住这句话,你可以少走很多弯路。

Show Case

image-20200116203233594

当然,说再多,吹再好,也没多大用。我上面讲的 Class 和 Hooks 的优缺点,网上的也有很多人讲,大家也肯定都看过。

用程序员的交流方式,就是“Talk is cheap,Show me the code.”。

亮剑吧!

接下来,我会用一个例子,让你折服,拜倒在 Hooks 的石榴裙下。如果你不服,咱们单独撕~

网络请求组件实现

image-20200116214124140

接下来,我们来实现一个最最最常见的组件。该组件接收 userId,然后发起网络请求,获得用户信息。

说白了,就是最简单的发起网络请求的组件。我们先用 Class 来实现看看。

image-20200116214639300

这段代码,是最简单的网络请求。

  • 定义一个 username 状态。
  • componentDidMount 的时候发起网络请求。
  • 网络请求结束,更新 username。

美滋滋。但是少了点东西。网络请求,我们肯定要维护一个 loading 状态,保证用户体验比较好。

那我们加上吧。

image-20200116214918755

这张图,我们增加了 loading 状态,在网络请求发起前,置为 true,在网络请求结束后,置为 false。

美滋滋。但是还是少点东西。userId 变化后,我要重新发起网络请求吧。

我们再加点代码吧。

image-20200116215101730

我们增加了对 userId 变化的监听,如果 userId 变化后,重新发起请求。

这次稳了吧?

不不不,还不够。如果 userId 连续变化了 5 次,发送了 5 个网络请求,我们要保证总是最后一次网络请求有效。也就是经常说的的“竞态处理”或者“时序控制”。

我加!加还不行吗!

image-20200116215409524

其实到这里,有些同学已经懵了。“你说的时序控制,听着很有道理,但我平时都没处理过这个问题,我看下你怎么实现的。”

确实,时序控制不算一个简单的问题,很多新手都不会解决这个问题。

稳了!到这里你觉得稳了吧。

还是年轻啊,小伙子。

image-20200116220003295

如果用上面的代码来玩,你可能会偶尔碰到上面的警告。这个警告是怎么造成的呢?我说一下你就明白了。下面四个步骤执行,必会报警告:

  1. 组件加载
  2. 发起网络请求
  3. 组件卸载
  4. 网络请求请求成功,触发 setState

看出问题了吗?组件已经卸载了,还去 setState,造成了内存溢出。

怎么解决呢?

image-20200116220311200

在组件卸载的时候,放弃最后一次请求。

到这里为止,我们就完成了一个完美的网络请求。这次真结束了!

看下写了多少行代码。

image-20200116220531399

除去空格,我们写了 38 行代码。实话说,38 行代码我能忍,但是这些逻辑我忍不了!回想下我们处理了多少逻辑:

  • 网络请求
  • loading
  • userId 变化重新发起请求
  • 竞态处理
  • 组件卸载放弃网络请求

关键这些逻辑是没办法复用的!每个项目可能有数十上百个组件会发网络请求,我就要写几十,几百遍这样的逻辑。想想我都难受。

说实话,我在写项目的时候经常会偷懒。要不就不写 loading,要不就不管竞态,要不就不管最后的内存溢出警告。

你有没有和我一样呢?嘿嘿。

言归正传,接下来就邀请 Hooks 登场了。

image-20200116221123031

三下五除二,我们用 Hooks 实现了刚才所有的逻辑。

image-20200116221212124

17 行!代码量减少了 50% 以上。好像还行!

但是,别忘了,Hooks 最重要的能力就是逻辑复用!这些逻辑我们完全可以封装起来!我们把刚才的逻辑全部封装起来!

image-20200116221359407

useAsync 封装了刚才我们说的所有功能,一行代码完成了网络请求。

最后整个组件会长这样。

image-20200116221605411

哇!我自己都佩服自己!简直了!美呆了,帅毙了,感觉自己无敌了!提前完成工作,下班回家!

image-20200116221755193

通过这个例子,我想证明一个论点:“使用 Hooks 封装逻辑的能力,可以极大提高开发效率”。

Umi Hooks

这时候你肯定要问,useAsync 在哪里?给我瞧瞧?

image-20200116221941442

useAsync

useAsync 在这里,快来瞧,快来看啦!

useAsync 是 Umi Hooks 库的核心 Hooks 之一,Umi Hooks 提供了大量提炼自业务的 Hooks。一行代码真的可以实现很多功能!

Umi Hooks 在这里在这里!你懂的~~

image-20200116222352082

当然,useAsync 不止包含上面说的功能,还支持“手动触发”执行,还支持“轮询”!

只要简单的配置一个 pollingInterval ,就能轮询发送请求了。快去试试啦

接下来我们会介绍几个更牛的 Hooks 给大家认识!

useAntdTable

image-cc2f4b087aca

AntD 的 Table 组件,想必大家在项目中经常用到吧!除了刚才异步请求的所有逻辑外,你还得处理其它的逻辑。

image-20200116232857059

比如维护 page、pageSize、sorter、filter 的状态,还得考虑搜索条件变化后,重置 page 到第一页。这些逻辑光想想就头疼了,别说写了。

现在一行代码就可以实现了!useAntdTable,封装了所有的逻辑,只要一行代码!如图上所示,你只要 ...tableProps,就可以了。这也许就是幸福的味道吧~

useLoadMore

加载更多的场景,比如下面动图的场景,想必大家在工作中都写过。

image-22fa47992b6f

这样一个加载更多的场景,我们要维护多少状态?写多少行逻辑?本来我打算写个 Class 实现的例子贴出来的,但是我放弃了,因为太难了~~

随便想想要处理的逻辑:

  • 第一次加载时候的 loading
  • 加载更多时候的 loading
  • 维护 page 和 pageSize
  • 网络请求
  • 是不是加载全了
  • 搜索条件变化后,重置到第一页。
  • .....

脑壳疼,真的脑壳疼。我会写,但是写起来真的好累。

还没完,一般产品同学还会要求,上拉加载更多......

image-cf629db68ebc

这时候我们还得监听滚动位置,如果快到底了,触发加载更多。脑壳更疼了!

image-20200116235013101

Umi Hooks 听到了你的求救,派出 useLoadMore 来拯救你了。一行代码就可以实现所有的功能!一个小时变一分钟,又可以早点下班了。

useDynamicList

image-20200116235455431

还有更好用的,比如 useDynamicList,下面的动态列表,一行代码搞定。

image-16556dfcf0e8

useBoolean

不仅是上面讲到的各种复杂逻辑可以封装。简单的逻辑封装起来也是极其好用的,比如 Boolean 值的管理。

我们一般控制 Modal,Popover 等显示隐藏的时候,都要维护一个 visible 状态。大概会是这样。

image-20200116235651552

这样的逻辑,你写过多少遍?没有几千也有几百吧!

image-20200116235850057

以后你就可以用 useBoolean 咯!

More

image-20200116235957789

不仅是上面讲到的这些,我们还有很多很多的 Hooks。

比如 useSearch,就封装了通常异步搜索场景的逻辑,比如 debounce。

比如 useVirtualList,就封装了虚拟列表的逻辑。

image-20200117000249183

比如 useMouse,封装了监听鼠标位置的逻辑。

比如 useKeyPress,封装了监听键盘按键的逻辑。

image-20200117000412951

30+ Hooks 供您选择,并且我们仍然处于婴儿期,快速发展中。我们的愿景就是:封装别人的逻辑,让别人无逻辑可写。

未来规划

image-20200117001130397

更多的 Hooks 开发

如上面所述,我们现在还处于婴儿期,需要不断汲取能量,更多的 Hooks 正在路上!要实现“让别人无逻辑可写”的目标,还需继续奋斗。

更强大的 useRequest

image-20200117001209280

大家应该都听过 useSWR 吧?是 zeit 公司开发的一个专门做网络请求的 Hooks,提供了很多新颖的思路,给了我们非常大的启发,github star 就像坐火箭一样。但在实际项目使用中,还是会有很多地方不符合蚂蚁内部的体系。但是它给我们非常大的启发,基于 swr 的思路,我们可以实现更强大的 useRequest!图上的能力,我们都要!

useRequest 目前已经处于内测期了,下个版本将会与大家见面!我们的目标是:所有的网络请求,只用 useRequest 就够了!

Hooks 生态

目前社区上 Hooks 相关的基础教程、进阶教程、原理深入、常见问题等文档都比较分散,我们准备向 Hooks 生态发展,提供各式各样的文章。以后学习 Hooks,使用 Hooks,找 Umi Hooks 就对了。

当然,生态方面目前正在规划中,预计年后启动。

总结

image-20200117001711649

Umi Hooks,你值得拥有。

我们目前处于发展阶段,欢迎大家一起共建。

你可以提 idea,我们负责实现。

你可以提 issue,我们负责改 bug。

你可以提 PR,将你封装的 Hooks 分享给大家,让更多人收益。

❤️期待您的参与。