概述
一、背景
Vue 3.0
已经出到3.0.5
了,React Hooks
大家也用的如日中天。整天大家都在讨论Hooks
,Hooks
,那么Hooks式的编程到底有什么好处?
还记得当时Vue3.0 beta
版本发布的时候,社区多少的反对声音:
- 意大利面代码结构吐槽:
“太失望了。杂七杂八一堆丢在 setup 里,我还不如直接用 react”
我的天,3.0 这么搞的话,代码结构不清晰,语义不明确,无异于把 vue 自身优点都扔了
怎么感觉代码结构上没有 2.0 清晰了呢 这要是代码量上去了是不是不好维护啊
- 抄袭 React 吐槽:
抄来抄去没自己的个性
有 react 香吗?越来越像 react 了
在我看来,Vue 黑暗的一天还远远没有过去,很多人其实并没有认真的去看Vue-Composition-Api
文档中的 动机 章节,本文就以这个章节为线索,从 代码结构、底层原理 等方面来一一打消大家的一些顾虑。本篇文章与你使用Vue3.0或者React无关,因为在Hooks思想上,他们本质上是一致的。
一、Vue3.0的设计动机
大如Vue3
这种全球热门的框架,任何一个 breaking-change
的设计一定有它的深思熟虑和权衡,那么 composition-api
出现是为了解决什么问题呢?这是一个我们需要首先思考明白的问题。
首先抛出 Vue2
的代码模式下存在的几个问题。
- 随着功能的增长,复杂组件的代码变得越来越难以维护。 尤其发生你去新接手别人的代码时。 根本原因是 Vue 的现有 API 通过「选项」组织代码,但是在大部分情况下,通过逻辑考虑来组织代码更有意义。
- 缺少一种比较「干净」的在多个组件之间提取和复用逻辑的机制。
- 类型推断不够友好。
逻辑重用
相信很多接触过React Hook
的小伙伴已经对这种模式下组件间逻辑复用的简单性有了一定的认知,自从 React 16.7
发布以来,社区涌现出了海量的 Hook
轮子,以及主流的生态库react-router
,react-redux
等等全部拥抱 Hook
,都可以看出社区的同好们对于Hook
开发机制的赞同。
其实组件逻辑复用在 React 中是经历了很长的一段发展历程的, mixin -> HOC & render-props -> Hook
,mixin 是 React
中最早启用的一种逻辑复用方式,因为它的缺点实在是多到数不清,而后面的两种也有着自己的问题,比如增加组件嵌套啊、props
来源不明确啊等等。可以说到目前为止,Hook
是相对完美的一种方案。
当然,我的一贯风格就是上代码对比,我就拿 HOC
来说吧,Github
上的一个真实的开源项目里就出现了这样的场景:
HOC 对比 Hook
class MenuBar extends React.Component {
// props 里混合着来自各个HOC传入的属性,还有父组件传入的属性。
handleClickNew() {
const readyToReplaceProject = this.props.confirmReadyToReplaceProject(
this.props.intl.formatMessage(sharedMessages.replaceProjectWarning)
);
this.props.onRequestCloseFile();
if (readyToReplaceProject) {
this.props.onClickNew(this.props.canSave && this.props.canCreateNew);
}
this.props.onRequestCloseFile();
}
handleClickRemix() {
this.props.onClickRemix();
this.props.onRequestCloseFile();
}
handleClickSave() {
this.props.onClickSave();
this.props.onRequestCloseFile();
}
handleClickSaveAsCopy() {
this.props.onClickSaveAsCopy();
this.props.onRequestCloseFile();
}
}
export default compose(
// 国际化
injectIntl,
// 菜单
MenuBarHOC,
// react-redux
connect(mapStateToProps, mapDispatchToProps)
)(MenuBar);
没错,这里用 compose
函数组合了好几个 HOC
,其中还有 connect
这种 接受几个参数返回一个接受组件作为函数的函数 这种东西,如果你是新上手(或者哪怕是 React
老手)这套东西的人,你会在 「这个 props
是从哪个 HOC
里来的?」,「这个 props
是外部传入的还是 HOC
里得到的?」这些问题中迷失了大脑,最终走向堕落(误)。
不谈 HOC
,我的脑子已经快炸开来了,来看看用Hook
的方式复用逻辑是怎么样的场景吧?
function MenuBar(props) {
// props 里只包含父组件传入的属性
const { show } = props;
// 菜单
const { onClickRemix, onClickNew } = useMenuBar();
// 国际化
const { intl } = useIntl();
// react-redux
const { user } = useSelector((store) => store.user);
}
export default MenuBar;
一切都变得很明朗,我可以非常清楚的知道这个方法的来源,intl
是哪里注入进来的,点击了 useMenuBa
r 后,就自动跳转到对应的逻辑,维护和可读性都极大的提高了。
当然,这是一个比较「刻意」的例子,但是相信我,我在 React 开发中已经体验过这种收益了。随着组件的「职责」越来越多,只要你掌握了这种代码组织的思路,那么你的组件并不会膨胀到不可读。
常见的请求场景
再举个非常常见的请求场景。
在 Vue2 中如果我需要请求一份数据,并且在loading
和error
时都展示对应的视图,一般来说,我们会这样写:
<template>
<div v-if="error">failed to load</div>
<div v-else-if="loading">loading...</div>
<div v-else>hello {{fullName}}!</div>
</template>
<script>
import { createComponent, computed } from 'vue'
export default {
data() {
// 集中式的data定义 如果有其他逻辑相关的数据就很容易混乱
return {
data: {
firstName: '',
lastName: ''
},
loading: false,
error: false,
},
},
async created() {
try {
// 管理loading
this.loading = true
// 取数据
const data = await this.$axios('/api/user')
this.data = data
} catch (e) {
// 管理error
this.error = true
} finally {
// 管理loading
this.loading = false
}
},
computed() {
// 没人知道这个fullName和哪一部分的异步请求有关 和哪一部分的data有关 除非仔细阅读
// 在组件大了以后更是如此
fullName() {
return this.data.firstName + this.data.lastName
}
}
}
</script>
这段代码,怎么样都谈不上优雅,凑合的把功能完成而已,并且对于loading
、error
等处理的可复用性为零。
数据和逻辑也被分散在了各个option
中,这还只是一个逻辑,如果又多了一些逻辑,多了data
、computed
、methods
?如果你是一个新接手这个文件的人,你如何迅速的分辨清楚这个method
是和某两个data
中的字段关联起来的?
让我们把zeit/swr的逻辑照搬到 Vue3 中,
看一下swr在 Vue3 中的表现:
<template>
<div v-if="error">failed to load</div>
<div v-else-if="loading">loading...</div>
<div v-else>hello {{fullName}}!</div>
</template>
<script>
import { createComponent, computed } from 'vue'
import useSWR from 'vue-swr'
export default createComponent({
setup() {
// useSWR帮你管理好了取数、缓存、甚至标签页聚焦重新请求、甚至Suspense...
const { data, loading, error } = useSWR('/api/user', fetcher)
// 轻松的定义计算属性
const fullName = computed(() => data.firstName + data.lastName)
return { data, fullName, loading, error }
}
})
</script>
就是这么简单,对吗?逻辑更加聚合了。
对了,顺嘴一提, use-swr
的威力可远远不止看到的这么简单,随便举几个它的能力:
- 间隔轮询
- 请求重复数据删除
- 对于同一个 key 的数据进行缓存
- 对数据进行乐观更新
- 在标签页聚焦的时候重新发起请求
- 分页支持
- 完备的 TypeScript 支持
等等等等……而这么多如此强大的能力,都在一个小小的 useSWR()
函数中,谁能说这不是魔法呢?
类似的例子还数不胜数。
umi-hooks
react-use
代码组织
上面说了那么多,还只是说了 Hook 的其中一个优势。这其实并不能解决「意大利面条代码」的问题。当逻辑多起来以后,组件的逻辑会糅合在一起变得一团乱麻吗?
从获取鼠标位置的需求讲起
我们有这样一个跨组件的需求,我想在组件里获得一个响应式的变量,能实时的指向我鼠标所在的位置。
Vue 官方给出的自定义 Hook 的例子是这样的:
import { ref, onMounted, onUnmounted } from "vue";
export function useMousePosition() {
const x = ref(0);
const y = ref(0);
function update(e) {
x.value = e.pageX;
y.value = e.pageY;
}
onMounted(() => {
window.addEventListener("mousemove", update);
});
onUnmounted(() => {
window.removeEventListener("mousemove", update);
});
return { x, y };
}
在组件中使用:
import { useMousePosition } from "./mouse";
export default {
setup() {
const { x, y } = useMousePosition();
// other logic...
return { x, y };
},
};
就这么简单,无需多言。在任何组件中我们需要「获取响应式的鼠标位置」,并且和我们的「视图层」关联起来的时候,仅仅需要简单的一句话即可。并且这里返回的 x、y 是由 ref 加工过的响应式变量,我们可以用watch
监听它们,可以把它们传递给其他的自定义 Hook 继续使用。几乎能做到你想要的一切,只需要发挥你的想象力。
二、Vue Hook对比 React Hook
其实 React Hook 的限制非常多,比如官方文档中就专门有一个章节介绍它的限制:
- 不要在循环,条件或嵌套函数中调用 Hook
- 确保总是在你的 React 函数的最顶层调用他们。
- 遵守这条规则,你就能确保
Hook
在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的useState
和useEffect
调用之间保持hook
状态的正确。
而 Vue 带来的不同在于:
- 与
React Hooks
相同级别的逻辑组合功能,但有一些重要的区别。 与React Hook
不同,setup
函数仅被调用一次,这在性能上比较占优。 - 对调用顺序没什么要求,每次渲染中不会反复调用
Hook
函数,产生的的GC
压力较小。 - 不必考虑几乎总是需要
useCallback
的问题,以防止传递函数prop
给子组件的引用变化,导致无必要的重新渲染。 React Hook
有臭名昭著的闭包陷阱问题(甚至成了一道热门面试题,omg),如果用户忘记传递正确的依赖项数组,useEffect
和useMemo
可能会捕获过时的变量,这不受此问题的影响。Vue
的自动依赖关系跟踪确保观察者和计算值始终正确无误。- 不得不提一句,
React Hook
里的「依赖」是需要你去手动声明的,而且官方提供了一个eslint
插件,这个插件虽然大部分时候挺有用的,但是有时候也特别烦人,需要你手动加一行丑陋的注释去关闭它。
我们认可 React Hooks
的创造力,这也是 Vue-Composition-Api
的主要灵感来源。上面提到的问题确实存在于React Hook
的设计中,我们注意到Vue
的响应式模型恰好完美的解决了这些问题。
三、结语
我对于 React
和 Vue
都非常的喜欢。他们都有着各自的优缺点,本文绝无引战之意。两个框架都很棒!只是各有优缺点而已。React 的 Immutable 其实也带来了很多益处,并且 Hook 的思路还是 Facebook 团队的大佬们首创的,真的是很让人赞叹的设计。
参考:https://zhuanlan.zhihu.com/p/133819602
最后
以上就是失眠水壶为你收集整理的我为什么要使用Hooks?的全部内容,希望文章能够帮你解决我为什么要使用Hooks?所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复