react 哲学_「 思考 」 React Hooks 的设计哲学

f4cbc49c755d5333599fbde1002b9306.png

背景

React Hooks 已经出来有段时间了, 很多小伙伴或多或少都用过。

今天呢,我们就回头再看一下这个东西,思考一下,这个东西为什么会出现,它解决了什么问题, 以及背后的设计理念。

正文

如果你有 Hooks 的使用经验, 可以思考一下这两个问题:Hooks 为什么会产生

Hooks 解决了什么问题

我们先主要围绕这两点展开讨论。

1. Hooks 为什么会产生

在正式开始这个话题前, 我们先回顾一下react的发家史.

2013年5月13号, 在JS Conf 上发布了第一个版本0.3.0.

我第一次接触React 是在2015年, 对createClass语法记忆犹新:

4db208c5009db1ff5b8e1322969753eb.png

createClass 是当时第一种用于创建 React 组件的语法, 因为当时Javascript 还没有成形的 Class 体系。

这种情况在2015年1月17号得到了改变。

这时候, ES6 正式发布,支持 Class 语法。

这时候面临一个选择:

继续用自家的 createClass 呢 还是使用新的 ES6 Class?

毕竟 createClass 又不是不能用, 而且用着还挺顺手。

最后, React 还是选择了拥抱新趋势, 使用ES6 Class。

并在 React 0.13.1 Beta1版本, 开始支持使用原生Javasciprt Class语法。 我找到了如下说明:

4c95fa50ff3d26253b9ac26cfc0b9208.png

大意就是: 我们并不想自己单独搞一套, 大家习惯怎么用, 我们就怎么搞。

基于这个变化, React Class 组件就变成了我们之前经常见到的这样:

261f8b3d963772d1a6b6e81524a77fa2.png

是不是很熟悉。

生命周期方法和之前保持一致,变化的是组件初始化的部分。

从原本的getInitinalState 变成了constructor。

经历过这个阶段的小伙伴肯定对以下代码段非常熟悉:constructor(props) {

super(props); // 🤮🤮🤮

this.state = {};

this.xxx = this.xxx.bind(this); // 😢😢😢

}

在组件初始化的时候, 必须手动super(props) 一下, 至于为什么这么做, 本文不做讨论, 有兴趣的可以看一下这篇译文: 为什么要写Super(props)。

Class Fields 也允许我们跳过这一步:class xxx extends React.Component {

state = {};

}

到这一步, 已经解决了两个令人难受的点:super

bind

已经足够OK了, 是吧。

其实还不够。

我们在编写react 应用的时候, 难以避免的一件事就是: 拆分react 组件。

把一个复杂的UI视图拆分成不同的模块, 然后组合在一起。

这也是 react 本身推崇的理念: 万物皆可是组件。

这种设计很棒棒, 但依旧有很多问题。

我认为主要是亮点:组件内逻辑的割裂

逻辑复用困难

1. 先说 逻辑上的割裂:

基于生命周期的设计, 使得我们经常写出逻辑割裂的代码:

3db8f1730c29d4fabdf481d2080af618.png

同样的逻辑, 我们需要在不同的生命周期中去实现。

在一个大型的app 中, 类似的逻辑会有很多, 掺杂在一起。

后人要去修改的时候, 不得不使用上下左右反复横跳之术, 令人十分痛苦。

2. 逻辑复用困难

我们都知道, react 应用其实是由一些列 UI 套件组合而成的, 这些套件有些有状态, 有些没有状态。

eb80ffdebbfc6a6ef01c2e9b990e4408.png

把这些组件组合在一起,处理好复用, 其实是有一定困难的。

比如,假设在另外一个组件,有和上图相似的逻辑, 怎么办呢?

Copy & Paste 显然是可以的, 但却不是最优雅的。

React 本身并不提供解决方案,但是机智的网友们逐渐摸索出了一些改善这个问题的方法:High Order Components

Render Props

以High Order Components 为例, 看一下最简的例子

为组件都加入一个data属性, 然后返回这个增强的组件:

4c4ff8aeaa37f7b5bf218b97f7d61deb.png

逻辑并不复杂。

回到我们最初的那个例子, 现在要把这部分逻辑抽离出来, 实现一个WithRepos 高阶方法:

d7476988ad49a2e2106d90ad4cff4999.png

使用的时候, 包裹一下就可以了:

a18bace5a523583b2bf66fded789ee82.png

Render Props 也是同样的目的, 不作赘述, 可参考:Render Props

这两种做法, 都可以改善逻辑复用的困境,但同时又引入了新的问题。

还是以高级组件为例, 比如我们对一个组件要加入多个增强功能,显而易见, 代码就变成了:export default withA(

withB (

withC (

withD (

Component

)

)

)

)

Render Props 也一样, 这两种模式都会限制你的组件结构,随着功能的增加, 包裹的层数越来越多,陷入所谓的 wrapper hell之中。

这种情况并不是我们想要的。

写到这里, 我们进行一个简单的总结, 整理一下遇到的问题:我们使用 Class 语法来生成组件,super语法很烦, 但是可以跳过。

this 让人懵逼。

基于生命周期的设计, 容易造成逻辑上的割裂, 不容易维护。

React 没有以后好的模式来解决逻辑复用问题。

所以, 迫切需要一种新的模式来解决以上这些问题。

理想中, 这种模式要具备以下特点:简单好用

足够灵活

方便组合

扩展性强

那么, 这种新的模式该如何设计呢?

1cdf2d7c394dc7412e045d3817727e34.png

而且, Javascript 本身对 function 也是天生友好。

所以, 这时候要做的就是:抛弃 React.Component

拥抱 function

3ad0735dfc89eb43763cee020a284bce.png

在这个背景下, Hooks 应运而生。

2. Hooks 解决了什么问题

拥抱 Function, 面前就有三座大山需要解决:组件 State

生命周期

逻辑复用难题

2.1 State

abd0d3641706a1991d47642c9e36dd53.png

State Hook 的标准形式是返回一个元组, 包含两个元素:

5c6ea1cc8360308f14073c05e92adb6d.png

使用起来也非常的简单:

a7a12ca694ec14f9ee76d099cdaaf26d.png

至此,有了state hook, function 就具备了基础的状态管理能力:组件 State ✅

生命周期

逻辑复用难题

2.2 Lifecyles

这一步, 我们先忘记传统 Class Component 的生命周期方法, 想一下, 如何在 Function 中实现类似的能力。

我们要用这样的能力去实现,比如:状态变更

数据获取

等等

基于这样的思考, useEffect 问世了。

useEffect 赋予了Function 在组件内部执行副作用的能力。

7ada7ecbe3f2bba0e6c1de9bc14999e2.png

就形式而言, Effect Hook 接受两个参数:一个 function.

一个可选的 array.

简单的例子:

43a3d5a4e742e3b04a7e31dd42359f15.png

当 username 变化时, 就修改document title.

⚠️ 注意

有时候,你也许会不经意间把 Effect 写成一个 async 函数:

11aa544c48fac5049c271f4881b587f9.png

强烈建议你不要这样做。

useEffect 约定:Effect 函数要么没有返回值,要么返回一个 Cleanup 函数。

而这里 async 函数会隐式地返回一个 Promise,直接违反了这一约定,会造成不可预测的结果。

至此, Function 组件也有了应该具备的生命周期方法。组件 State ✅

生命周期 ✅

逻辑复用难题

只剩最后一个课题: 逻辑复用。

2.3 Sharing Non-Visual Logic

传统而言, 我们把页面拆分成一个个UI组件, 然后把这个UI组件组合起来。 这种情况最终也不可避免的诞生了 HOC & Render Props 等模式来改善逻辑复用问题。

你可能会想, React Hooks 可能会有新的解决办法。

办法的确是有, 它就是Custom Hooks.

你可以把需要复用的逻辑抽成一个个单独的Custom Hook, 在需要用的地方使用。

举个例子:

把需要复用的逻辑抽离:

8a40dfc2f8db09cdc2b5f347e604a063.png

在需要用到的地方使用:

1999bfdc113096d34cfa779a8d884c28.png

这样, 我们就轻松而又自然的实现了逻辑的复用。组件 State ✅

生命周期 ✅

逻辑复用难题 ✅

至此, 三个难题得以解决。

Hooks 的价值所在

回头我们再看这个问题, 其实从始至终, 要解决的问题只有一个:提升代码复用以及组合的能力。

顺带的, 也一定程度上提升了组件的内聚性, 减少了维护成本:

963777fc7892480da1c0a518a1520165.gif

681662e756ac820ca7dd807cf1a98fbe.png

相关的逻辑都在单独的一块, 改需求的时候,不用需要施展上下左右反复横跳之术,提早了下班时间, 多好。

结尾

知其然,也要知其所以然。

我们在自己平时的搬砖运动中, 也要考虑自己的代码是否具备一下能力:简单好用

足够灵活

方便组合

扩展性强

不坑自己, 也不坑别人, 早点下班。

好了, 别的就不扯了, 希望这篇文章能给你一些启发和思考。

才疏学浅, 文章若有错误, 欢迎留言之正。

如果你觉得这篇内容对你挺有启发,可以:点个「在看」,让更多的人也能看到这篇内容。

关注公众号「前端e进阶」,掌握前端面试重难点,公众号后台回复「加群」和小伙伴们畅聊技术。

9932ad19c024f8e40a1b07c613f3d4ef.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值