vue3的composition-api实践总结

更新日期: 2021-07-16阅读: 1.1k标签: vue3
因为向往已久vue3的开发方式,但是组内有很多历史项目,并且我们受制于ie的支持,所以我们决定在vue2中引入composition-api,来使用他的新特性。在使用过程中,我们遇到了很多问题,也积累了一些经验,所以记录下。
composition-api

首先给大家介绍一下composition-api,他是通过函数的形式,将vue的功能特性暴露给我们使用


虽然一下子看上去很多,但是只需要先掌握加粗的这一部分,就可以体验到组合式api的魅力了,其他的可以先做了解等到你真的需要使用它们的时候在做深入。

reactive 用来将对象转变为响应式的,与vue2的observable类似,ref用来获得单独或者为基础数据类型获得响应性。为什会会有两个获得响应性的api呢稍后我们将具体说明。computed、watch,provide、inject不用怀疑和vue2中做的是一样的事情。

你一定注意到下面这些加了on开头的生命周期钩子函数,没错在组合式api中,这就是他们注册的方式。但是为什么不见了beforeCreate和created呢?因为setup就是在这个阶段执行的,而setup就是打开组合式api世界的大门。你可以把setup理解为class的constructor,在vue组件的创建阶段,把我们的相关逻辑执行,并且注册相关的副作用函数。

现在我们说回ref和reactive。

  • reactive在官网中的说明,接受一个对象,返回对象的响应式副本。ref在官网中的描述"接受一个内部值并返回一个响应式且可变的 ref 对象。
  • ref 对象具有指向内部值的单个 property.value"。

听着很绕口,简单来讲就是reactive可以为对象创建响应式而ref除了对象,还可以接收基础数据类型,比如string、boolean等。
那为什么会有这种差异呢?在vue3当中响应式是基于proxy实现的,而proxy的target必须是复杂数据类型,也就是存放在堆内存中,通过指针引用的对象。其实也很好理解,因为基础数据类型,每一次赋值都是全新的对象,所以根本无法代理。那么如果我们想取得简单类型的响应式怎么办呢?这时候就需要用到ref。

class RefImpl<T> {
private _value: T

public readonly __v_isRef = true

constructor(private _rawValue: T, public readonly _shallow = false) {
this._value = _shallow ? _rawValue : convert(_rawValue)
}

get value() {
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}

set value(newVal) {
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}
}
...
const convert = <T extends unknown>(val: T): T =>
isObject(val) ? reactive(val) : val
...

ref通过创建内部状态,将值挂在value上,所以ref生成的对象,要通过value使用。重写get/set获得的监听,同时对对象的处理,也依赖了reactive的实现。

由此,ref并不只是具有对基本数据类型的响应式处理能力,他也是可以处理对象的。所以我认为ref和reactive的区分并不应该只是简单/复杂对象的区分,而是应该用编程思想区分的。我们应该避免,把reactive 当作data在顶部将所有变量声明的想法,而是应该结合具体的逻辑功能,比如一个控制灰度的Flag那他就应该是一个ref,而分页当中的页码,pageSize,total等就应该是一个reactive声明的对象。也就是说一个setup当中可以有多出响应变量的声明,而且他们应当是与逻辑紧密结合的.

接下来我先用一个分页的功能,用选项式和组合式api给大家对比一下。

一些例子

<template>
<div>
<ul class="article-list">
<li v-for="item in articleList" :key="item.id">
<div>
<div class="title">{{ item.title }}</div>
<div class="content">{{ item.content }}</div>
</div>
</li>
</ul>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="pageSizes"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
>
</el-pagination>
</div>
</template>

<script>
import { getArticleList } from '@/mock/index';
export default {
data() {
return {
articleList: [],
currentPage: 1,
pageSizes: [5, 10, 20],
pageSize: 5,
total: 0,
};
},
created() {
this.getList();
},
methods: {
getList() {
const param = {
currentPage: this.currentPage,
pageSizes: this.pageSizes,
pageSize: this.pageSize,
};
getArticleList(param).then((res) => {
this.articleList = res.data;
this.total = res.total;
});
},
handleSizeChange(val) {
this.pageSize = val;
this.getList();
},
handleCurrentChange(val) {
this.currentPage = val;
this.getList();
},
},
};
</script>

这还是我们熟悉到不能在熟悉的分页流程,在data中声明数据,在method中提供修分页的方法。当我们用composition-api实现的时候他就成了下面的样子。

<script>
import { defineComponent, reactive, ref, toRefs } from "@vue/composition-api";
import { getArticleList } from "@/mock/index";
export default defineComponent({
setup() {
const page = reactive({
currentPage: 1,
pageSizes: [5, 10, 20],
pageSize: 5,
total: 0,
});
function handleSizeChange(val) {
page.pageSize = val;
getList();
}
function handleCurrentChange(val) {
page.currentPage = val;
getList();
}

const articleList = ref([]);
function getList() {
getArticleList(page).then((res) => {
articleList.value = res.data;
page.total = res.total;
});
}
getList();
return {
...toRefs(page),
articleList,
getList,
handleSizeChange,
handleCurrentChange,
};
},
});
</script>

这是以composition-api的方式实现的分页,你会发现原本的data,method,还有声明周期等选项都不见了,所有的逻辑都放到了setup当中。通过这一个简单的例子,我们可以发现原本分散在各个选项中的逻辑,在这里得到了聚合。这种变化在复杂场景下更为明显。在复杂组件中,这种情况更加明显。而且当逻辑完全聚集在一起,这时候,将他们抽离出来,而且抽离逻辑的可以在别处复用,至此hook就形成了。

hook形态的分页组件

// hooks/useArticleList.js
import { ref } from "@vue/composition-api";
import { getArticleList } from "@/mock/index"; // mock ajax请求

function useArticleList() {
const articleList = ref([]);
function getList(page) {
getArticleList(page).then((res) => {
articleList.value = res.data;
page.total = res.total;
});
}
return {
articleList,
getList,
};
}
export default useArticleList;

// hooks/usePage.js
import { reactive } from "@vue/composition-api";

function usePage(changeFn) {
const page = reactive({
currentPage: 1,
pageSizes: [5, 10, 20],
pageSize: 5,
total: 0,
});
function handleSizeChange(val) {
page.pageSize = val;
changeFn(page);
}
function handleCurrentChange(val) {
page.currentPage = val;
changeFn(page);
}
return {
page,
handleSizeChange,
handleCurrentChange,
};
}
export default usePage;

// views/List.vue
import { defineComponent, toRefs } from "@vue/composition-api";
import usePage from "@/hooks/usePage";
import useArticleList from "@/hooks/useArticleList";
export default defineComponent({
setup() {
const { articleList, getList } = useArticleList();
const { page, handleSizeChange, handleCurrentChange } = usePage(getList);
getList(page);
return {
...toRefs(page),
articleList,
getList,
handleSizeChange,
handleCurrentChange,
};
},
});

在hook使用过程中我们也踩过很多坑

  1. hook中的异步问题
    因为hook本质上就是函数,所以灵活度非常高,尤其是在涉及异步的逻辑中,考虑不全面就很有可能造成很多问题。hook是可以覆盖异步情况的,但是必须在setup当中执行时返回有效对象不能被阻塞。

我们总结了两种异步的风格,通过一个简单的hook为例

  • 外部没有其他依赖,只是交付渲染的响应变量 对于这种情况,可以通过声明、对外暴露响应变量,在hook中异步修改的方式
// hooks/useWarehouse.js
import { reactive,toRefs } from '@vue/composition-api';
import { queryWarehouse } from '@/mock/index'; // 查询仓库的请求
import getParam from '@/utils/getParam'; // 获得一些参数的方法
function useWarehouse(admin) {
const warehouse = reactive({ warehouseList: [] });
const param = { id: admin.id, ...getParam() };
const queryList = async () => {
const { list } = await queryWarehouse(param);
list.forEach(goods=>{
// 一些逻辑...
return goods
})
warehouse.warehouseList = list;
};
return { ...toRefs(warehouse), queryList };
}
export default useWarehouse;// components/Warehouse.vue
<template>
<div>
<button @click="queryList">queryList</button>
<ul>
<li v-for="goods in warehouseList" :key="goods.id">
{{goods}}
</li>
</ul>
</div>
</template>

<script>
import { defineComponent } from '@vue/composition-api';
import useWarehouse from '@/hooks/useWarehouse';
export default defineComponent({
setup() {
// 仓库保管员
const admin = {
id: '1234',
name: '张三',
age: 28,
sex: 'men',
};
const { warehouseList, queryList } = useWarehouse(admin);
return { warehouseList, queryList };
},
});
</script>
  • 外部具有依赖,需要在使用侧进行加工的 可以通过对外暴露Promise的方式,使外部获得同步操作的能力 在原有例子上拓展,增加一个需要处理的更新时间属性
// hooks/useWarehouse.js
function useWarehouse(admin) {
const warehouse = reactive({ warehouseList: [] });
const param = { id: admin.id, ...getParam() };
const queryList = async () => {
const { list, updateTime } = await queryWarehouse(param);
list.forEach(goods=>{
// 一些逻辑...
return goods
})
warehouse.warehouseList = list;
return updateTime;
};
return { ...toRefs(warehouse), queryList };
}
export default useWarehouse;// components/Warehouse.vue
<template>
<div>
...
<span>nextUpdateTime:{{nextUpdateTime}}</span>
</div>
</template>

<script>
...
import dayjs from 'dayjs';
export default defineComponent({
setup() {
...
// 仓库保管员
const admin = {
id: '1234',
name: '张三',
age: 28,
sex: 'men',
};
const { warehouseList, queryList } = useWarehouse(admin);
const nextUpdateTime = ref('');
const interval = 7; // 假设更新仓库的时间间隔是7天
const queryHandler = async () => {
const updateTime = await queryList();
nextUpdateTime.value = dayjs(updateTime).add(interval, 'day');
};
return { warehouseList, nextUpdateTime, queryHandler };
},
});
</script>
  1. this的问题
    因为setup是beforecreate阶段,不能获取到this,虽然通过setup的第二个参数context可以获得一部分的能力。是我们想要操作诸如路由,vuex这样的能力就收到了限制,最新的router@4、vuex@4都提供了组合式的api。

但是由于vue2的底层限制我们没有办法使用这些hook,但是我们可以通过引用实例的方式获得一定的操纵能力,也可以通过getCurrentInstance获得组件实例,上面挂载的对象。

由于composition-api中的响应式虽然底层原理与vue相同都是通过object.defineproperty改写属性实现的,但是具体实现方式存在差异,所以在setup当中与vue原生的响应式并不互通。这也导致即使我们拿到了相应的实例,也没有办法监听它们的响应式。如果有这方面的需求,只能在选项配置中使用。

总结


通过vue3组合式、与hook的能力。我们的代码风格有了很大的转变,逻辑更加聚合、纯粹。复用性能力得到了提升。项目整体的维护性有了显著的提高。这也是我们即便在vue2的项目中,也要使用composition-api引入vue3新特性的原因。若有收获,就点个赞吧


链接: https://www.fly63.com/article/detial/10558

vue3.x 新特性 - CompositionAPI

安装 vue-cli3,在使用任何 @vue/composition-api 提供的能力前,必须先通过 Vue.use() 进行安装,安装插件后,您就可以使用新的 Composition API 来开发组件了。

Vue3数据响应系统

Vue3 就是基于 Proxy 对其数据响应系统进行了重写,现在这部分可以作为独立的模块配合其他框架使用。数据响应可分为三个阶段: 初始化阶段 --> 依赖收集阶段 --> 数据响应阶段

快速进阶Vue3.0

在2019.10.5日发布了Vue3.0预览版源码,但是预计最早需要等到 2020 年第一季度才有可能发布 3.0 正式版。新版Vue 3.0计划并已实现的主要架构改进和新功能:

Vue 3 对 Web 应用性能的改进

有关即将发布的 Vue.js 的第 3 个主要版本的信息越来越多。通过下面的讨论,虽然还不能完全确定其所有内容,但是我们可以放心地认为,它将是对当前版本(已经非常出色)的巨大改进。 Vue 团队在改进框架 API 方面做得非常出色

Vue3 中令人兴奋的新功能

用新的 Vue 3 编写的程序效果会很好,但性能并不是最重要的部分。对开发人员而言,最重要的是新版本将会怎样影响我们编写代码的方式。如你所料,Vue 3 带来了许多令人兴奋的新功能。值得庆幸的是

200 行从零实现 vue3

emmm 用半天时间捋顺了 vue3 的源码,再用半天时间写了个 mini 版……我觉得我也是没谁了,vue3 的源码未来一定会烂大街的,我们越早的去复现它,就……emm可以越早的装逼hhh

从 Proxy 到 Vue 源码,深入理解 Vue 3.0 响应系统

10 月 5 日,尤雨溪在 GitHub 开放了 Vue 3.0 处于 pre-alpha 状态的源码,这次 Vue 3.0 Updates 版本的更新,将带来五项重大改进:速度体积、可维护性、面向原生、易用性

Vue 的数据响应式(Vue2 及 Vue3)

从一开始使用 Vue 时,对于之前的 jq 开发而言,一个很大的区别就是基本不用手动操作 dom,data 中声明的数据状态改变后会自动重新渲染相关的 dom。换句话说就是 Vue 自己知道哪个数据状态发生了变化及哪里有用到这个数据需要随之修改。

在Vue2与Vue3中构建相同的组件

Vue 开发团队终于在今天发布了 3.0-beta.1 版本,也就是测试版。通常来说,从测试版到正式版,只会修复 bug,不会引入新功能,或者删改老功能。所以,如果你对新版本非常感兴趣,或者有新项目即将上马,不妨尝试一下新版本

Vue3中的Vue Router初探

对于大多数单页应用程序而言,管理路由是一项必不可少的功能。随着新版本的Vue Router处于Alpha阶段,我们已经可以开始查看下一个版本的Vue中它是如何工作的。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!