原创声明:本文首发于公众号:前端琐话 (qianduansuohua),欢迎关注
Vue 3.0 中引入了一种新的代码编写方式,那就是 Composition API,这是有别于 Vue 2.0 Options API 的一种函数式 API。无需通过指定一长串选项来定义组件,Composition API 允许用户像编写函数一样自由地组合逻辑和代码。那么我们接下来就一起来看看 Composition API 是啥东东?
什么是 Composition API?
组合式 API:一组低侵入式的、函数式的 API,使得我们能够更灵活地「组合」组件的逻辑
我们看一个简单的范例
<template>
<button @click="increment">
Count is: {{state.count}}, double is: {{state.double}}
</button>
</template>
<script>
import {reactive, computed} from 'vue'
export default {setup() {
const state = reactive({
count: 0,
double: computed(() => state.count * 2),
})
function increment() {state.count++}
return {
state,
increment,
}
},
}
</script>
我们先来看下这段代码发生了啥?
import {reactive, computed} from 'vue'
Component API 是以函数的形式展示组件属性,所以第一步就是导入我们需要的函数。在我们的例子中,我们用 reactive
创建响应属性,用 computed
创建计算属性。
export default {setup() {
// ...
return {
state,
increment,
}
}
还有一个 setup
函数,setup
函数是一个新的组件选项。作为在组件内使用 Composition API 的入口点,如果 setup
返回一个对象,则对象的属性将会被合并到组件模板的渲染上下文,我们就可以在模板里使用对应的属性和方法。
为什么要引入 Composition API
在 Vue2 中我们采用 Options API 来写这个范例
<template>
<button @click="increment">
Count is: {{count}}, double is: {{double}}
</button>
</template>
<script>
export default {data() {
return {count: 0};
},
computed: {double() {return this.count * 2;}
},
methods: {increment() {this.count++;}
}
};
</script>
那在 Vue2 中如果我们要复用这个逻辑,我们可以通过诸如 mixins
、高阶组件或是无渲染组件 (通过作用域插槽实现的) 的模式达成。
首先我们来看下 mixins 的方式
import CounterMixin from './mixins/counter'
export default {mixins: [CounterMixin]
}
mixins
存在的问题是
- 渲染上下文中暴露的 property 来源不清晰。例如在阅读一个运用了多个 mixin 的模板时,很难看出某个 property 是从哪一个 mixin 中注入的。
- 命名空间冲突。mixin 之间的 property 和方法可能有冲突。
那么我们来看下作用域插槽的方式:
<template>
<Counter v-slot="{count, increment}">
{{count}}
<button @click="increment">Increment</button>
</Counter>
</template>
有了 scoped slots,我们就可以通过 v -slot 属性准确地知道我们可以访问哪些属性,这样就更容易理解代码了。这种方法的缺点是,我们只能在模板中访问,而且只能在 Counter 组件作用域中使用。
除此之外,高阶组件和无渲染组件需要额外的有状态的组件实例,从而使得性能有所损耗。
ok,是时候让 Composition API 登场了
function useCounter() {
const state = reactive({
count: 0,
double: computed(() => state.count * 2),
});
function increment () { count.value++}
return {
state,
incrememt
}
}
export default {setup () {const { state, increment} = useCounter()
return {
state,
increment
}
}
}
更加优雅了,不是吗?相比较而言:
- 暴露给模板的 property 来源十分清晰,因为它们都是被组合逻辑函数返回的值。
- 不存在命名空间冲突,可以通过解构任意命名
- 不再需要仅为逻辑复用而创建组件实例
- 仅依赖它的参数和 Vue 全局导出的 API,而不是依赖其微妙的 this 上下文
除了方便逻辑提取与复用之外,Composition API 带给我们的实际上更多的是一种新的代码编写思维。
当要去理解一个组件时,我们更加关心的是“这个组件是要干什么”(即代码背后的意图) 而不是“这个组件用到了什么选项”。基于选项的 API 撰写出来的代码自然采用了后者的表述方式,然而对前者的表述并不好 。
Options API 选项的强行分离为展示背后的逻辑关注点设置了障碍。此外,在处理单个逻辑关注点时,我们必须不断地在选项代码块之间“跳转”,以找到与该关注点相关的部分。
比如上面的例子中,基于 Options API 的方式我们必须在 data、computed、methods 三个选项中跳转,来完成这段逻辑。而通过 Composition API 的方式我们把相同逻辑关注点的代码并列在一起,形成了一个独立的逻辑函数。
存在问题
当然 Composition API 的引入也存在一定的弊端。
组合式 API 在代码组织方面提供了更多的灵活性,但它也需要开发人员更多地自我克制来“正确地完成它”,组合式 API 会让没有经验的人编写出面条代码。
在 Options API 中实际上形成了一种强制的约定:
- props 里面设置接收参数
- data 里面设置变量
- computed 里面设置计算属性
- watch 里面设置监听属性
- methods 里面设置事件方法
你会发现 Options API 都约定了我们该在哪个位置做什么事,这在一定程度上也强制我们进行了代码分割。
现在用 Composition API,不再这么约定了,于是乎,代码组织非常灵活,如果作为一个新手,或者不深入思考的码农,那么在逻辑越来越复杂的情况下,setup 代码量越来越多,同样 setup 里面的 return 越来越复杂,势必会落入“面条代码”的斡旋之中。
我们期望的是 setup()
函数现在只是简单地作为调用所有组合函数的入口
export default {setup() {
// 网络状态
const {networkState} = useNetworkState()
// 文件夹状态
const {folders, currentFolderData} = useCurrentFolderData(networkState)
const folderNavigation = useFolderNavigation({
networkState,
currentFolderData,
})
const {favoriteFolders, toggleFavorite} = useFavoriteFolders(currentFolderData)
const {showHiddenFolders} = useHiddenFolders()
const createFolder = useCreateFolder(folderNavigation.openFolder)
// 当前工作目录
resetCwdOnLeave()
const {updateOnCwdChanged} = useCwdUtils()
// 实用工具
const {slicePath} = usePathUtils()
return {
networkState,
folders,
currentFolderData,
folderNavigation,
favoriteFolders,
toggleFavorite,
showHiddenFolders,
createFolder,
updateOnCwdChanged,
slicePath,
}
},
}
当然针对这些,官方也有一定的接纳策略,Vue 3.0 里 Composition API 并不是默认的方案,而是沿用 Vue 2.X 的 Options API,Composition API 将被定为为高级特性,因为它旨在解决的问题主要出现在大型应用程序中 。
参考资料:
- Vue 组合式 API
欢迎关注微信公众号