【知乎 - Vue Function-based API RFC】https://zhuanlan.zhihu.com/p/68477600
【 vuejs/composition-api - api文档】https://vue-composition-api-rfc.netlify.com/#summary
【github - vuejs/composition-api】https://github.com/vuejs/composition-api
根据 vue 作者尤雨溪去年发布的 vue 3.0 开发路线,vue 3.0 很可能在 2019 年下半年与大家见面。
与此同时,今年下半年初,Vue 团队先后对外发布了 vue-function-api 和 vue-compisition-api 的 RFC,供开发者提前体验 vue 3.0 的新特性,并且希望通过这些预览版的 API,来征求广大开发者的意见与反馈,从而为 vue 3.0 的后续开发和落地打下坚实与稳定的基础。
2018年9 月 30 日,尤雨溪在 medium 个人博客上发布了 Vue 3.0 的开发路线,我们不妨看看 Vue 3.0 将会有怎样的发展。
兼容
按照尤雨溪的说法,因为 Vue 3.0 是主要版本,所以会包含一些重大变更。不过,开发组会非常重视兼容性问题,他们也将尽快开始传达这些重大变更,并做了这样的保证:除了渲染函数 API 和作用域插槽语法之外的所有内容都将保持不变,或者通过兼容性构建让其与 2.x 保持兼容。
总的来说,Vue 3.0 虽然会对顶级 API 进行重大的修整,但依然会保持与 2.x 的兼容。此外,2.x 的最后一个次要版本将成为 LTS,并在 3.0 发布后继续享受 18 个月的 bug 和安全修复更新。
重构
为了实现更清晰、更易维护的源代码架构,尤雨溪表示将从头开始重写 3.0,并将一些内部功能分解为单独的包,以便隔离复杂性。
新的源代码结构(有可能会变化)
此外,代码库现在也用 TypeScript 编写,相信 TypeScript 的类型系统和 IDE 的支持将让新的代码贡献者更容易做出有意义的贡献。
其他的改进
虽然 Vue 3.0 尚未发布,但是其处于 RFC 阶段的 Composition API 已经可以通过插件 @vue/composition-api 进行体验了。
安装 @vue/composition-api 插件以后,按照文档在 main.js 引用便开启了 Composition API 的能力。
import Vue from 'vue' import App from './App.vue'
import VueCompositionApi from '@vue/composition-api'
Vue.config.productionTip = false
Vue.use(VueCompositionApi)
new Vue({
render: h => h(App),
}).$mount('#app')
回到 App.vue,从 @vue/composition-api 插件引入 { reactive, computed, toRefs } 三个函数:
import { reactive, computed, toRefs } from '@vue/composition-api'
仅保留 components: { ... } 选项,删除其他的,然后写入 setup() 函数:
export default {
components: { ... },
setup () {}
}
接下来,我们将会在 setup() 函数里面重写之前的逻辑。
首先定义数据。
为了让数据具备“响应式”的能力,我们需要使用 reactive() 或者 ref() 函数来对其进行包装,关于这两个函数的差异,会在后续的章节里面阐述,现在我们先使用 reactive() 来进行。
在 setup() 函数里,我们定义一个响应式的 data 对象,类似于 2.x 风格下的 data() 配置项。
setup () {
const data = reactive({
todoList: [],
showingStatus: 'all',
onShowList: computed(() => {
if (data.showingStatus === 'all') {
return data.todoList
} else if (data.showingStatus === 'completed') {
return data.todoList.filter(({ completed }) => completed)
} else if (data.showingStatus === 'uncompleted') {
return data.todoList.filter(({ completed }) => !completed)
}
})
})
}
其中计算属性 onShowList 经过了 computed() 函数的包装,使得它可以根据其依赖的数据的变化而变化。
接下来定义方法。
在 setup() 函数里面,对之前的几个操作选项的方法稍加修改即可直接使用:
function submit (content) {
data.todoList.push({
completed: false,
content,
id: parseInt(Math.random(0, 1) * 100000)
})
}
function onStatusChanged (status) {
data.showingStatus = status
}
function toggleStatus ({ isChecked, id }) {
data.todoList.forEach(item => {
if (item.id === id) {
item.completed = isChecked
}
})
}
function onItemDelete (id) {
let index = 0
data.todoList.forEach((item, i) => {
if (item.id === id) {
index = i
}
})
data.todoList.splice(index, 1)
}
与在 methods: {} 对象中定义的形式所不同的地方是,在 setup() 里的方法不能通过 this 来访问实例上的数据,而是通过直接读取 data 来访问。
最后,把刚刚定义好的数据和方法都返回出去即可:
return {
...toRefs(data),
submit,
onStatusChanged,
toggleStatus,
onItemDelete,
}
这里使用了 toRefs() 给 data 对象包装了一下,是为了让它的数据保持“响应式”的。
Vue 其中一个被人诟病得很严重的问题就是逻辑复用。随着项目越发的复杂,可以抽象出来被复用的逻辑也越发的多。但是 Vue 在 2.x 阶段只能通过 mixins 来解决(当然也可以非常绕地实现 HOC,这里不再展开)。mixins 只是简单地把代码逻辑进行合并,如果需要对逻辑进行追踪将会是一个非常痛苦的过程,因为繁杂的业务逻辑里面往往很难一眼看出哪些数据或方法是来自 mixins 的,哪些又是来自当前组件的。
另外一点则是对 TypsScript 的支持。为了更好地进行类型推断,虽然 2.x 也有使用 Class 风格的 ts 实现方案,但其冗长繁杂和依赖不稳定的 decorator 的写法,并非一个好的解决方案。受到 React Hooks 的启发,Vue Composition API 以函数组合的方式完成逻辑,天生就适合搭配 TypeScript 使用。
至于 Options API 和 Composition API 孰优孰劣的问题,在本文所展示的例子中其实是比较难区分的,原因是这个例子的逻辑实在是太过简单。但是如果深入思考的话不难发现,如果项目足够复杂,Composition API 能够很好地把逻辑抽离出来,每个组件的 setup() 函数所返回的值都能够方便地被追踪(比如在 VSCode 里按着 cmd 点击变量名即可跳转到其定义的地方)。这样的能力在维护大型项目或者多人协作项目的时候会非常有用,通用的逻辑也可以更细粒度地共享出去。
关于 Composition API 的设计理念和优势可以参考官网的 Motivation 章节。
如果脑洞再开大一点,Composition API 可能还有更酷的玩法。
const state = Vue.observable({ count: 0 })
const Demo = {
render(h) {
return h('button', {
on: { click: () => { state.count++ }}
}, `count is: ${state.count}`)
}
}
上述例子中,当响应式的 state.count 被修改以后,会触发 watch() 函数里面的回调。基于此,也许我们可以利用这个特性去处理其他平台的视图更新问题。微信小程序开发框架 mpvue 就是通过魔改 Vue 的源码来实现小程序视图的数据绑定及更新的,如果拥有了 Composition API,也许我们就可以通过 reactive() 和 watch() 等方法来实现类似的功能,此时 Vue 将会是位于数据和视图中间的一层,数据的绑定放在 reactive(),而视图的更新则统一放在 watch() 当中进行。