前言
高阶组件这个概念在 React 中一度非常流行,但是在 Vue 的社区里讨论的不多,本篇文章就真正的带你来玩一个进阶的骚操作。
先和大家说好,本篇文章的核心是学会这样的思想,也就是智能组件
和木偶组件
的解耦合,没听过这个概念没关系,下面会详细说明。
这可以有很多方式,比如slot-scopes
,比如未来的composition-api
。本篇所写的代码也不推荐用到生产环境,生产环境有更成熟的库去使用,这篇强调的是思想
,顺便把 React 社区的玩法移植过来皮一下。
不要喷我,不要喷我,不要喷我!! 此篇只为演示高阶组件的思路,如果实际业务中想要简化文中所提到的异步状态管理,请使用基于slot-scopes
的开源库 vue-promised
另外标题中提到的20k
其实有点标题党,我更多的想表达的是我们要有这样的精神,只会这一个技巧肯定不能让你达到20k
。但我相信只要大家有这样钻研高级用法,不断优化业务代码,不断提效的的精神,我们总会达到的,而且这一天不会很远。
我的web前端学习交流群点击进入1045267283,欢迎加入!
例子
本文就以平常开发中最常见的需求,也就是异步数据的请求为例,先来个普通玩家的写法:
<template>
<div v-if="error">failed to load</div>
<div v-else-if="loading">loading...</div>
<div v-else>hello {
{result.name}}!</div>
</template>
<script>
export default {
data() {
return {
result: {
name: '',
},
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
}
},
}
</script>
一般我们都这样写,平常也没感觉有啥问题,但是其实我们每次在写异步请求的时候都要有loading
、error
状态,都需要有取数据
的逻辑,并且要管理这些状态。
那么想个办法抽象它?好像特别好的办法也不多,React 社区在 Hook 流行之前,经常用HOC
(high order component) 也就是高阶组件来处理这样的抽象。
高阶组件是什么?
- 说到这里,我们就要思考一下高阶组件到底是什么概念,其实说到底,高阶组件就是:
一个函数接受一个组件为参数,返回一个包装后的组件
。
在 React 中
- 在 React 里,组件是
Class
,所以高阶组件有时候会用装饰器
语法来实现,因为装饰器
的本质也是接受一个Class
返回一个新的Class
。 - 在 React 的世界里,高阶组件就是
f(Class) -> 新的Class
。
在 Vue 中
在 Vue 的世界里,组件是一个对象,所以高阶组件就是一个函数接受一个对象,返回一个新的包装好的对象。
类比到 Vue 的世界里,高阶组件就是f(object) -> 新的object
。
智能组件和木偶组件
如果你还不知道木偶
组件和智能
组件的概念,我来给你简单的讲一下,这是 React 社区里一个很成熟的概念了。木偶
组件: 就像一个牵线木偶一样,只根据外部传入的props
去渲染相应的视图,而不管这个数据是从哪里来的。智能
组件: 一般包在木偶
组件的外部,通过请求等方式获取到数据,传入给木偶
组件,控制它的渲染。
一般来说,它们的结构关系是这样的:
<智能组件>
<木偶组件 />
</智能组件>
它们还有另一个别名,就是 容器组件
和 ui组件
,是不是很形象。
实现
具体到上面这个例子中(如果你忘了,赶紧回去看看,哈哈),我们的思路是这样的,
- 高阶组件接受
木偶组件
和请求的方法
作为参数 - 在
mounted
生命周期中请求到数据 - 把请求的数据通过
props
传递给木偶组件
。
- 接下来就实现这个思路,首先上文提到了,
HOC
是个函数,本次我们的需求是实现请求管理的HOC
,那么先定义它接受两个参数,我们把这个HOC
叫做withPromise
。
并且loading
、error
等状态,还有加载中
、加载错误
等对应的视图,我们都要在新返回的包装组件
,也就是下面的函数中return 的那个新的对象
中定义好。
const withPromise = (wrapped, promiseFn) => {
return {
name: "with-promise",
data() {
return {
loading: false,
error: false,
result: null,
};
},
async mounted() {
this.loading = true;
const result = await promiseFn().finally(() => {
this.loading = false;
});
this.result = result;
},
};
};