面向未来编程,如何在 Vue2 中使用 Vue3 的语法[实践篇]
面向未来编程(Future-Oriented Programming),vue-function-api 提供开发者在 Vue2.x 中使用 Vue3 的语法逻辑开发应用。(为方便以下以 Vue2 表示)
本文不对文档 api 对过多说明,仅讨论在项目实践中遇到的问题。比较两者的区别是对 Vue3 写法最快的了解,下面通过对比同一个功能在 Vue2 与 Vue3 的区别。
场景是这样的,一个列表页,上部分是条件筛选,下部分是 table,table 行内会有删除,修改(以 dialog 形式展示)功能,template 部分无区别,这里不做赘述。
准备工作
安装 vue-function-api
npm install vue-function-api --save
# 或者
yarn add vue-function-api
在项目中引入 main.js
import Vue from 'vue'
import Element from 'element-ui'
import { plugin } from 'vue-function-api' // <--- 引入 vue-function-api
import App from './App'
import router from './router'
import store from './store'
Vue.use(Element)
Vue.use(plugin) // <--- 这样就可以在单文件组件中使用函数式 API了
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App),
}).$mount('#app')
准备工作后,就可以安安静静的做比较了。
data
在 Vue2 中,首先要定义 data, data 中我们定义一个 query 对象用于条件筛选,citys 用于条件筛选中城市(动态获取下拉),statusMap 用于筛选条件中状态的下拉,list 用于 table。
export default {
data() {
return {
query: {
city: null,
name: null,
status: null
},
citys: [],
list: [],
statusMap: [{
value: 1,
label: '启用'
}, {
value: 2,
label: '禁用'
}]
}
}
Vue3 中是这样定义
import { value } from 'vue-function-api'
export default {
setup() {
const query = value({
city: null,
name: null,
status: null
})
const citys = value([])
const list = value([])
const statusMap = value([{
value: 1,
label: '启用'
}, {
value: 2,
label: '禁用'
}])
return {
query,
citys,
list,
statusMap
}
}
}
引入 setup, 在其中做 data 做的事情,细心的同学会发现在 setup 后需要 return 所定义的变量,其中也并非要将所有的变量都放入 return 中,放入 return 中是为了能够在 template 中使用,即只需 return template 中依赖的变量
computed
上文中说到 table 中有删除,编辑功能,正常的系统中这类功能是需要有权限的人才能操作,这里我们假设已经将 permissions 存入 vuex 中,使用的时需要通过 mapGetters 取出 permissions(此处为举例这样实现,正常的权限要比这个复杂)。
在 Vue2 中是这样写
import { mapGetters } from 'vuex'
export default {
// 此处先省略 data 部分...
computed: {
...mapGetters(['permissions']),
canUpdate() {
return this.permissions.includes('update')
},
canDelete() {
return this.permissions.includes('delete')
},
}
// ...
}
Vue3:目前 vue-function-api 还不支持 vuex map 的方式 导出 vuex 中的状态,这里我们从 Vue root 中取出 store 中的数据
import { computed } from 'vue-function-api'
export default {
setup(props, ctx) {
const { $store } = ctx.root
// 此处先省略 data 部分...
const permissions = computed(() => $store.getters.permissions)
const canUpdate = () => permissions.includes('update')
const canDelete = () => permissions.includes('delete')
return {
canUpdate,
canDelete // <--- 这里只导出 template 的依赖
}
}
}
在 template 中, 编辑,删除的按钮上 分别加入 v-if="canUpdate"
, v-if="canDelete"
,即可实现对应权限的人才能看到对应的按钮,了解更多 context。
methods & lifecycle
基于上文中的描述,因为是一个列表页,所以结合 lifecycle 一起做一下对比。
在 Vue2 中
export default {
// ...
methods: {
async fetchCity() {
const response = await fetchCityApi()
this.citys = response.data
},
async fetchList() {
const response = await fetchListApi(this.query)
this.list = response.data
},
async delete(id) {
const response = await deleteItem(id)
const { status, msg } = response.data
if (status !== 'ok') return this.$message.error(msg)
this.$message({
type: 'success',
message: '删除成功'
})
},
confirm(id) {
this.$confirm(`确认删除`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.delete(id)
}).catch(() => {
this.$message({
type: 'info',
message: '已取消'
})
})
},
detail(id) {
this.$router.push({
path: '/detail',
query: { id }
})
}
}
}
created() {
this.fetchCity()
this.fetchList()
}
}
接下来看 Vue3 中实现
import { computed, onCreated } from 'vue-function-api'
export default {
setup(props, ctx) {
const { $message, $router, $confirm, $store } = ctx.root // <--- setup() 中不可以使用 this 访问当前组件实例, 可以通过 setup 的第二个参数 context 来访问 vue2.x API 中实例上的属性。
// 此处先省略 data 部分...
// method
const fetchCity = async () => {
const response = await fetchCityApi()
citys.value = response.data // <--- 划重点,通过 value() wrap初始化的变量在 setup 内部引用值或者修改值都要加 .value
},
const fetchList = async () => {
const response = await fetchListApi(query.value)
list.value = response.data
},
const delete = async id => {
const response = await deleteItem(id)
const { status, msg } = response.data
if (status !== 'ok') return $message.error(msg)
$message({
type: 'success',
message: '删除成功'
})
},
confirm(id) {
$confirm(`确认删除`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delete(id)
}).catch(() => {
$message({
type: 'info',
message: '已取消'
})
})
},
detail(id) {
$router.push({
path: '/detail',
query: { id }
})
}
// lifecycle
onCreated(() => {
fetchCity()
fetchList()
})
return {
fetchCity,
fetchList,
confirm,
detail // <--- 这里只导出 template 的依赖
}
}
}
是不是感觉切换到 Vue3 也没那么难
watch
watch 的例子在一个列表页中可能没有什么使用场景,上文中说到列表页上面是一个条件筛选,有城市下拉,状态下拉,当然正常情况下,我们都是通过 select 的 change 事件调用 fetchList,这里我就为了举例而这么写,实际项目自己衡量利弊。
在 Vue2 中
export default {
// ...
watch: {
query: {
handler: function(val) {
this.fetchList()
},
{ deep: true }
}
}
// 或者不嫌麻烦这样写...
watch: {
'query.city': function(val) {
this.fetchList()
},
'query.name': function(val) {
this.fetchList()
},
'query.status': function(val) {
this.fetchList()
}
}
// ...
}
在 Vue3 中
import { watch } from 'vue-function-api'
export default {
setup() {
// ...
watch(
query,
val => {
fetchList()
},
{ deep: true }
)
// 或者
watch(
() => query.value,
val => {
fetchList()
},
{ deep: true }
)
// ...
}
}
通过以上对比,你应该对 Vue3 写法有一定理解了,因为是以一个列表页为 demo对比,没有用到 provide、 inject,想了解的同学可以到 provide, inject
, 还有 state 相当于 Vue.observable
.
对比完整代码
Vue2
export default {
data() {
return {
query: {
city: null,
name: null,
status: null
},
citys: [],
list: [],
statusMap: [{
value: 1,
label: '启用'
}, {
value: 2,
label: '禁用'
}]
}
},
computed: {
...mapGetters(['permissions']),
canUpdate() {
return this.permissions.includes('update')
},
canDelete() {
return this.permissions.includes('delete')
},
},
methods: {
async fetchCity() {
const response = await fetchCityApi()
this.citys = response.data
},
async fetchList() {
const response = await fetchListApi(this.query)
this.list = response.data
},
async delete(id) {
const response = await deleteItem(id)
const { status, msg } = response.data
if (status !== 'ok') return this.$message.error(msg)
this.$message({
type: 'success',
message: '删除成功'
})
},
confirm(id) {
this.$confirm(`确认删除`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.delete(id)
}).catch(() => {
this.$message({
type: 'info',
message: '已取消'
})
})
},
detail(id) {
this.$router.push({
path: '/detail',
query: { id }
})
}
},
created() {
this.fetchCity()
this.fetchList()
},
watch: {
query: {
handler: function(val) {
this.fetchList()
},
{ deep: true }
}
}
Vue3
import { value, computed, onCreated } from 'vue-function-api'
export default {
setup(props, ctx) {
const { $store, $message, $router, $route } = ctx.root
// reactive state
const query = value({
city: null,
name: null,
status: null
})
const citys = value([])
const list = value([])
const statusMap = value([{
value: 1,
label: '启用'
}, {
value: 2,
label: '禁用'
}])
// computed
const permissions = computed(() => $store.getters.permissions)
const canUpdate = () => permissions.includes('update')
const canDelete = () => permissions.includes('delete')
// method
const fetchCity = async () => {
const response = await fetchCityApi()
citys.value = response.data
},
const fetchList = async () => {
const response = await fetchListApi(query.value)
list.value = response.data
},
const delete = async id => {
const response = await deleteItem(id)
const { status, msg } = response.data
if (status !== 'ok') return $message.error(msg)
$message({
type: 'success',
message: '删除成功'
})
},
confirm(id) {
$confirm(`确认删除`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delete(id)
}).catch(() => {
$message({
type: 'info',
message: '已取消'
})
})
},
detail(id) {
$router.push({
path: '/detail',
query: { id }
})
}
// watch
watch(
query,
val => {
fetchList()
},
{ deep: true }
)
// lifecycle
onCreated(() => {
fetchCity()
fetchList()
})
return {
query,
citys,
list,
statusMap,
canUpdate,
canDelete,
fetchCity,
fetchList,
confirm,
detail
}
}
}
最后
这里贴一下 setup 中的第二个参数 context
,对象中的属性是 2.x 中的 vue 实例属性的一个子集。完整的属性列表:
- parent
- root
- refs
- slots
- attrs
- emit
像其他库 vuex,vue-router,ElementUI 的一些 $方法可以从 root 中取得
export default {
setup(props, ctx) {
const { $store, $router, $route, $message, $confirm } = ctx.root
}
}
最后的最后
笔者已用到正式环境,目前来看还没有什么问题,引用 vue-function-api 官方的说明
vue-function-api 会一直保持与 Vue3.x 的兼容性,当 3.0 发布时,您可以无缝替换掉本库。
vue-function-api 的实现只依赖 Vue2.x 本身,不论 Vue3.x 的发布与否,都不会影响您正常使用本库。
由于 Vue2.x 的公共 API 限制,vue-function-api 无法避免的会产生一些额外的内存负载。如果您的应用并不工作在极端内存环境下,无需关心此项。

