从React16.8开始,Hooks API正式被React支持,而就在最近,Vue做者尤雨溪翻译并发布了一篇本身的文章《Vue Function-based API RFC》,并在全文开头强调这是Vue 3.0最重要的RFC,并在文中提到html
Function-based API 受 React Hooks 的启发,提供了一个全新的逻辑复用方案。前端
能够简单的理解为,React 和 Vue 为了解决相同的问题,基于不一样的技术实现了类似的API。因此本文也将结合两种框架各自的特色,简单讲讲我的对Hooks的理解。react
在将来版本的规划里,React并不如Vue激进,React的文档里专门提到es6
并无从 React 中移除 class的计划。设计模式
而Vue却采起了不一样的升级策略,作好了抛弃大部分历史语法的准备数组
- 兼容版本:同时支持新 API 和 2.x 的全部选项;
- 标准版本:只支持新 API 和部分 2.x 选项。
为了回答这个问题,咱们先看看以前和如今的React组件划分产生了哪些变化。promise
早期的React组件能够依据“有没有状态(state)”分为数据结构
// 无状态组件
const Welcome = (props) => <h1>Hello, {props.name}</h1>;
// 有状态组件
class Welcome extends React.Component {
constructor(props) {
super(props);
this.state = {name: 'KuaiGou'};
}
render() {
return <h1>Hello, {this.state.name}</h1>;
}
}
复制代码
虽然class也能够不添加状态,但想要使一个函数组件具备状态,不得不将其转换成class组件。架构
直观来看,好像形成这种差别是由于在class里,咱们能经过this保存和访问“状态(state)”,而函数组件在其做用域内难以维持“状态(state)”,由于再次函数运行会重置其做用域内部变量,这种差别致使了咱们“不得不”使用class至今。并发
看来如何解决函数组件保存state的成了移除class这种“难以理解”的关键。
这就是我看见Hook API产生的第一个疑问。其实在React里,这并非问题,熟悉React Fiber的同窗应该知道,事实上state是保存到Fiber上的属性memoizedState上的,而并不算是class的this.state上。那状态问题就迎刃而解了,若是函数组件一样访问Fiber上的memoizedState属性,就能够解决这个问题。
基于Fiber架构,解决这个问题很是容易,将memoizedState看做一个普通的变量,那么Hook的原理就容易理解和实现了。
在文章[译] 理解 React Hooks中提到
记住,在 Hooks 的实现中也没有什么“魔术”。就像 Jamie 指出的那样,它像极了这个:
let hooks = null;
export function useHook() {
hooks.push(hookData);
}
function reactsInternalRenderAComponentMethod(component) {
hooks = [];
component();
let hooksForThisComponent = hooks;
hooks = null
}
复制代码
如Fiber同样,React实际上使用链表代替了数组这种数据结构,依次执行Hook,有兴趣的同窗能够去看下React源码。
但是,class目前也能良好的支撑业务迭代,到底有什么动力去从新学习Hooks?
针对这个问题,React文档提到了下面三点:
- 在组件之间复用状态逻辑很难
- 复杂组件变得难以理解
- 难以理解的 class
其实我以为第三点就是来凑数的,毕竟React推出至今一直用着class,再难用各位也都会了,会者不难难者不会嘛(反正对于刚入前端坑那时候的我来讲,没有啥是容易的)。
那就回答下一个问题,目前基于class实现的生命周期函数,是否真的会形成逻辑难以复用?
答案是NO!
不管高阶组件或是render props,都提供了很好的方式来达到聚合业务逻辑的目的,业务逻辑并不会被生命周期“分割”。
那究竟是哪里引入了复杂度?熟悉套娃的同窗...呸
熟悉Ajax、Promise的等异步API的同窗可能还记得“回调地狱”。相似的,高阶函数、render props等也极容易形成“嵌套地狱”,结合装饰器、函数式的compose等嵌套起来才是真的爽...一直嵌套一直爽...
可是,不管是什么地狱确定是很差的,那一块儿来看最后一个问题。
复杂组件变得难以理解
之因此回避前两个问题,是由于我我的认为,不管是class仍是HOC,它们都很好的解决了它们须要解决的问题,虽然生命周期函数将不少业务逻辑拆分的七零八碎,可是HOC却依旧能把它们集合在一块儿,仅考虑保留生命周期而言,就像Function-based同样(这是后话)。
因此咱们换一个思路不难发现,真正的问题是在于它们在抽象业务逻辑的时候貌似引入了没必要要的概念,才使得逻辑复用困难和难以理解。
这些概念致使了过多的嵌套,加深了组件层级,层级之间互相牵扯,就像我如今兜里的耳机线同样。
Hook独特之处在于化繁为简。
真正繁琐的是层级与层级之间的关系,我将借用React文档关于自定义Hook的例子说明这个问题
import React, { useState, useEffect } from 'react';
// 经过friendID订阅好友状态
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
复制代码
经过useFriendStatus这个自定义的hook,我能够很是轻松的在下面两个组件中实现逻辑的复用
// FriendStatus获取好友状态
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
// FriendListItem获取好友状态
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li>
);
};
复制代码
但是对于熟悉高阶组件的同窗来讲(不熟悉的同窗请看高阶组件)依旧能够轻松的提取一个名叫useFriendStatus的高阶组件
function useFriendStatus(WrappedComponent) {
return class extends React.Component {
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentDidUpdate(prevProps) {
ChatAPI.unsubscribeFromFriendStatus(
prevProps.friend.id,
this.handleStatusChange
);
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
render() {
return <WrappedComponent isOnline={isOnline} />; } } } 复制代码
而后,分别套上FriendStatus和FriendListItem依旧能够很是完美的多处复用此逻辑。
须要注意的是,高阶组件等概念仍是有不少特色和优势的,hooks并非万能的,hooks只是hooks而已
须要提到的是,官方文档在这里对比了新旧方案代码量的长度和复杂度,但其实在特定的业务状况下,确实不可避免会出现这种问题,我的认为这是次要矛盾(既然HOC已经封装了复杂度,还纠结里面长不长、复不复杂干啥)。
反而,这容易让刚接触Hooks的同窗忽略Hook最大的亮点,先看下面这段熟悉的代码(不熟悉的同窗看Promise 对象和async 函数)。
// async
const P1 = new Promise(Something)
const P2 = new Promise(Something)
export default async function () {
const res1 = await P1;
//do Something
const res2 = await P2;
// ...
return res2
}
复制代码
和刚才的Hook对比
// 以及 hook
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
...
return isOnline ? 'Online' : 'Offline';
}
复制代码
像不像...就问你像不像...(模仿团队某成员说话)
写(粘)了这么多代码,简单来讲,Hook解决的就是“嵌套地狱”的问题,正如async解决“回调地狱”同样。它们都作到了将原来不一样“维度”的代码封装到了同一维度,以达到更直观、透明的将“计算结果”传递下去的目的。
而class不得不借助高阶组件等等概念,解决代码复用等问题,可是因为引入额外的概念(函数)反而使得代码更加复杂,如今的class难以解决这个问题,因此他就被抛弃了。
问题来了,谁能够不引入别的概念,完成逻辑封装?
就是函数自己啊!!还要class干吗?!
综上,被抛弃和class生命周期函数致使的代码复杂度提高无关,Hook简化生命周期函数只是不过是举手之劳,并非什么重要的特性。
为何这里说这个不重要呢,下面将要讲的Function-based会帮助我解答这个问题。
Vue在组件化的道路上和React走过了大体相同的道路,这也就是为何有人问我Vue和React之间有什么区别的时候,鉴于我的水平,我是真的答不上来😂...
话题扯回来,它们都通过了下面几个阶段
- Mixins
- 高阶组件 (Higher-order Components)
- Renderless Components
抛开框架,咱们思考下“组件化”这个概念是否是为逻辑抽象服务的,咱们提取共有的逻辑,各处复用,各种设计模式最终落到实体我我的认为他就是“组件”。
同时了解Hooks和Function-based的同窗,应该不难发现他两巨大的差别,并且具备后发优点的Vue也解决了React Hooks还没有解决的问题(或者React并不认为这是问题,也不打算解决),但这里并不想对两种API的使用作过多的说明,毕竟它们的文档已经写的很是好了。
为了说明它们类似的地方,借用尤大文章中“用组合函数来封装鼠标位置侦听逻辑”的例子
function useMouse() {
const x = value(0)
const y = value(0)
const update = e => {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
// 在组件中使用该函数
const Component = {
setup() {
const { x, y } = useMouse()
// 与其它函数配合使用
const { z } = useOtherLogic()
return { x, y, z }
},
template: `<div>{{ x }} {{ y }} {{ z }}</div>`
}
复制代码
对比上文的“订阅好友状态”的React版本例子
import React, { useState, useEffect } from 'react';
// 经过friendID订阅好友状态
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
复制代码
不难发现,不管是Hooks仍是Function-based,都仅仅作了一件事。 那就是千方百计啊,把逻辑用一个函数包了起来,使得代码逻辑表现的很是天然,我相信这就是为何尤大坚决果断抛弃Vue存在多年的选项,直接用Function-based来代替它。
由于函数天生就是用来计算(状态)的。
解释下上面提到的“并不重要的生命周期”,观察上面的代码对比状况,有个很是明显的差别。在React里面被聚合(消失掉)的生命周期函数,具备后发优点的Vue仍旧保留下来了。
容易得出,逻辑有没有被生命周期切分,或者究竟在onMounted里面计算仍是在useEffect里计算并不重要,重要的是逻辑自己有没有被切分?
React、Vue也一直试图推出各类手段简化业务逻辑,可是正如Mixins、HOC、render-props以及本文说到的Hook和Function-based等。虽然它们都不是React或Vue原创的概念,但绝不影响初见时的惊艳,转念又会思考为何本身没有想到,说到底不就是在外面套一个函数嘛,总结起来就是。
背那么多API,依旧写很差代码。
快狗打车前端团队专一前端技术分享,按期推送高质量文章,欢迎关注点赞。