多灾多难的 2020 转眼过半。Vue 3.0 正式发布 beta 版,算是为数不多令人高兴的事情。
本文不谈 Vue 3.0 会带来什么性能提升,也不谈有无必要升级到 Vue 3.0。主要说说怎么在 Vue 2.x 利用 composition-api 怎么解决 option-base 的一些痛点。
我对 composition-api 的理解
对于业务开发来说,通常是面对一个个 Single-File-Component。为什么喜欢用 Vue,因为 Vue 帮我们在 Javascript 和 Views 间搭了一条高速列车。通过响应式的数据来反馈视图的变化,我们可以在 Vue 中改变数据的状态来改变视图。这么一来,我们需要利用 Vue 响应式的特性实现的逻辑,就必须在 Vue File 里面。一些简单且常用的逻辑,也只能在每个 Vue File 中 copy paste。
而 compostion-api,就是将这种响应式的特性,延伸到 Vue File 外。使得我们可以复用那些 copy paste work。
一个常见的业务实现,控制 Dialog 的显示隐藏
通常来说,控制 dialog 的显示隐藏很简单,在 data 中定义一个 showDialog 的状态即可,然后需要写一个方法去改变这个状态就能实现了。
// option-base
<template>
<dialog :visible="showDialog"></dialog>
</template>
<script>
export default {
data() {
return {
showDialog: false
}
},
methods: {
toggleDialog() {
this.showDialog = !this.showDialog
}
}
}
</script>
如果页面只有一个需要控制的 dialog,代码就如上方那么简单。但是,夸张点,一个页面有 10 个不同的类 dialog 交互需要实现,对于这么简单的逻辑,copy 就完事了。所以代码会变成下面这样
// option-base
<template>
<dialog0 :visible="showDialog0" @close="toggleDialog0"></dialog0>
<dialog1 :visible="showDialog1" @close="toggleDialog1"></dialog1>
<dialog2 :visible="showDialog2" @close="toggleDialog2"></dialog2>
...
<dialog9 :visible="showDialog9" @close="toggleDialog9"></dialog9>
</template>
<script>
export default {
data() {
return {
showDialog0: false,
showDialog1: false,
showDialog2: false,
// ...
showDialog9: false,
}
},
methods: {
toggleDialog0() {
this.showDialog0 = !this.showDialog0
},
toggleDialog1() {
this.showDialog1 = !this.showDialog1
},
toggleDialog0() {
this.showDialog2 = !this.showDialog2
},
// ...
toggleDialog0() {
this.showDialog9 = !this.showDialog9
},
}
}
</script>
这样,就能在一个页面上控制 10 这个类 dialog 的交互了。逻辑过于简单而忍不住 copy 😳
由于利用了 data 的响应式,我不能在外部抽象一个逻辑,去声明 show 个状态和返回一个改变状态的方法,这些逻辑算是在 option-base 的思维下最精简的代码了。当然对于这一系列状态可以用数组去优化,但是这些方法对于 composition-api 同样适用,所以不纳入比较范围,接下演示一下 composition-api 怎么复用这个响应式的状态
useToggle 逻辑
// reuse logic
export function useToggle(initState = false) {
const state = ref(initState)
const toggle = function(outerState) {
if (typeof outerState === 'boolean') {
state.value = outState
} else {
state.value = !state.value
}
}
return {
state,
toggle
}
}
业务使用
// compostion-api
<template>
<dialog0 :visible="showDialog0" @close="toggleDialog0"></dialog0>
<dialog1 :visible="showDialog1" @close="toggleDialog1"></dialog1>
<dialog2 :visible="showDialog2" @close="toggleDialog2"></dialog2>
...
<dialog9 :visible="showDialog9" @close="toggleDialog9"></dialog9>
</template>
<script>
export default {
setup() {
const { state: showDialog0, toggle: toggleDialog0 } = useToggle()
const { state: showDialog1, toggle: toggleDialog1 } = useToggle()
const { state: showDialog2, toggle: toggleDialog2 } = useToggle()
// ...
const { state: showDialog9, toggle: toggleDialog9 } = useToggle()
return {
showDialog0,
showDialog1,
showDialog2,
// ...
showDialog9,
toggleDialog0,
toggleDialog1,
toggleDialog2,
// ...
toggleDialog9,
}
},
}
</script>
可以看到 composition-api 可以优雅的复用这些简单的逻辑,但是对于 vue-template 来说是没什么改变的。所以我更愿意把 composition-api 看作更加灵活利用 Vue 的方案,而不是 Vue 3.0 代码的语法变革。只不过刚好它随着 Vue 3.0 的实现而出现在大家眼前。
这是一个状态的复用,如果像异步操作的 loading,success,error 的交互逻辑,触发方法时显示 loading,回调成功,显示数据,回调失败显示失败文案,这样的逻辑也是很常见,而往往也只能一次又一次的重新实现,因为它不难,而且在 option-base 中不好复用。大家看到这里可以思考一下怎么去复用这样的逻辑。
除此以外还有分页操作的逻辑,需要 total、currentPage、pageSize、data 几个状态,同时需要切换页码更新数据的方法,虽然复杂一丢丢,但是也只能一遍又一遍的重复实现。用上 composition-api 的思维,就可以复用这些逻辑,不仅项目内,甚至是整个 Vue 生态内的复用。切切实实的提示了业务开发的效率。
如何用上 composition-api
Vue 3.0 处于 bata 阶段,不是很适合公司应用上生产。但是令我大喊「真香」的不是 Vue 3.0 的性能优化,而是随之诞生的 composition-api 方案。所以 vue/composition-api 这个库是不错的选择。既可以提前熟悉 Vue 3.0 的语法,也能够在开发上看到比较实在的效率提示,当然前提是已经熟悉了 composition-api 的相关语法。
我踩过的坑,让大家少走一点弯路
使用 css-module 怎么获取 $style
issue: github.com/vuejs/compo…
import { getCurrentInstance } from '@vue/composition-api';
export default {
setup () {
const { $style } = getCurrentInstance()
}
}
需要注意的是,这个方法在 Vue 3.0 是不提供的,但是如果需要使用 js 来获取 css-module 注入的变量,就需要使用这个方法获取,如果在 template 中直接使用 $style 是不需要调用这个方法的
怎么使用 template ref 获取 dom 的引用
由于底层还是 Vue 2.x ,这块的实际逻辑和 Vue 3.0 的 RFC 是有出入的。详情可以看官方文档
而我是这样用的
const Demo = defineComponent({
setup(props, ctx) {
const [count, { inc, dec, set, reset }] = useCounter();
// useClickAway 返回一个 ref
const { element } = useClickAway(() => inc());
return {
element,
count,
inc,
dec,
set,
reset,
};
},
render(this: Vue & Inject) {
const { count } = this;
return (
<div>
click outside the button to increase counter
<br />
<button ref="element">count: {count}</button>
</div>
);
},
});
这里是实现了一个当点击 button 外部时触发一个事件。通常可以实现类似点击 dialog 外部关闭 dialog 的逻辑。
怎么获取响应式的路由呢
先说说根据文档是怎么获取 router 和 route 的
export default {
setup(props, ctx) {
const { $router, $route } = ctx.root
// 现在我想监听 route 上的 params id
// 第一次尝试
const id = computed(() => {
return route.params.id
})
watchEffet(() => {
console.log(id)
})
}
}
上面的代码,在改变了路由的 params 时,并不会作出响应,打印出 id 的。因为 route 只是一个普通 object,并没有作出响应式的处理。下面的代码才能在 route 改变时,代码也能触发响应
export default {
setup(props, ctx) {
const route = computed(() => ctx.root.$route)
const id = computed(() => {
return route.value.params.id
})
watchEffet(() => {
console.log(id)
})
}
}
推荐几个 hooks 库
- vue-use-web: github.com/Tarektouati…
封装了一些常用的浏览器 API 比如 FullScreen、VisibilityChange 等
- u3u/vue-hooks: github.com/u3u/vue-hoo…
注意封装了 router 和 vuex 的 hooks
另外由于 u3u/vue-hooks 的作者回应比较慢,所以本人 fork 了仓库,准备近期增加一些业务常用的 hooks,同时也更新了最新的 composition-api。大家可以来提需求 github.com/lianghx-319…
如果 u3u/vue-hooks 的作者看到,可以回应一下我,还是想继续维护原本的仓库