Vue Composition API重点汇总
1.1 小例子
<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>
1.2 目标
目前存在的问题
经验所得,Vue当前API会带来以下两类编程模型的限制:
- 复杂组件的代码逻辑较为难以推理,代码可读性不高。在某些情况下,通过逻辑考虑来组织代码更有意义。
- 缺少用于多个组件之间的提取和重用逻辑的简洁机制。
所以为在组织代码时给用户提供更大的灵活性,有以下目标:
- 可以将代码组织为每个函数都处理特定功能的函数,而不必总是通过选项来组织代码。
- API使在组件之间,甚至外部组件之间,提取和重用逻辑变得更加简单。
1.3 详细设计
API介绍
- Reactive State and Side Effects
声明一些变量
import { reactive } from 'vue'
// reactive state
const state = reactive({
count: 0
})
reactive与Vue.observable()2.x 中的当前API 等效,返回的state是所有Vue用户都应该熟悉的反应性对象。
Vue中反应状态的基本用例是我们可以在渲染期间使用它。由于依赖关系跟踪,当反应状态更改时,视图会自动更新。
在DOM中渲染某些内容被视为“副作用”:我们的程序正在修改程序本身(DOM)外部的状态。要应用并基于反应状态自动重新应用副作用,我们可以使用watchAPI:
import { reactive, watch } from 'vue'
const state = reactive({
count: 0
})
watch(() => {
document.body.innerHTML = `count is ${state.count}`
})
- Computed State and Refs
- 有时我们需要依赖于其他状态的状态-在Vue中,这是通过计算属性来处理的。
// 要直接创建一个计算值,我们可以使用computedAPI
import { reactive, computed } from 'vue'
const state = reactive({
count: 0
})
const double = computed(() => state.count * 2)
// 猜想 computed
function computed(getter) {
let value
watch(() => {
value = getter()
})
return value
}
而我们知道value如果是原始类型number,computed一旦返回,其内部更新逻辑的连接将丢失,将值分配给对象作为属性时,也会发生相同的问题。
为了确保我们始终可以读取计算的最新值,我们需要将实际值包装在一个对象中,然后返回该对象:
// 简化代码
function computed(getter) {
const ref = {
value: null
}
watch(() => {
ref.value = getter()
})
return ref
}
const double = computed(() => state.count * 2)
watch(() => {
console.log(double.value)
}) // -> 0
state.count++ // -> 2
这double是一个我们称为“ ref”的对象,因为它用作对其持有的内部值的反应性引用。
除了计算的引用外,我们还可以使用refAPI 直接创建普通的可变引用:
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
Ref
是此提案中引入的唯一“新”概念。引入它是为了将反应性值作为变量传递,而不必依赖对的访问 this
- Ref Unwrapping
- Usage in Components
- Lifecycle Hooks
参考
代码组织
-
保持代码井井有条的最终目的应该是使代码更易于阅读和理解。
-
“理解”代码,在于理解它运用数据、逻辑,最终解决的逻辑功能问题。
-
示例
VueClIUI文件浏览器
该组件必须处理许多不同的逻辑问题:- 跟踪当前文件夹状态并显示其内容
- 处理文件夹导航(打开,关闭,刷新…)
- 处理新文件夹的创建
- 仅切换显示收藏夹
- 切换显示隐藏文件夹
- 处理当前工作目录更改
而在具体代码实现的整个文件的代码组织中,单一逻辑功能的实现过程被分散在文件各个不同部分,导致组件可读性差、难以维护。
Composition API为我们提供了更为灵活并且模块化的代码组织结构。
可以通过以下方式编写“创建新文件夹”功能:
每个逻辑功能点的代码在组合函数中并置在一起。当在大型组件上工作时,这大大减少了对恒定“跳跃”的需求。合成功能也可以在编辑器中折叠,以使组件更易于扫描:
export default {
setup() { // ...
}
}
function useCurrentFolderData(networkState) { // ...
}
function useFolderNavigation({ networkState, currentFolderData }) { // ...
}
function useFavoriteFolder(currentFolderData) { // ...
}
function useHiddenFolders() { // ...
}
function useCreateFolder(openFolder) { // ...
}
setup()
现在,该函数主要用作调用所有组合函数的入口点:
export default {
setup () {
// Network
const { networkState } = useNetworkState()
// Folder
const { folders, currentFolderData } = useCurrentFolderData(networkState)
const folderNavigation = useFolderNavigation({ networkState, currentFolderData })
const { favoriteFolders, toggleFavorite } = useFavoriteFolders(currentFolderData)
const { showHiddenFolders } = useHiddenFolders()
const createFolder = useCreateFolder(folderNavigation.openFolder)
// Current working directory
resetCwdOnLeave()
const { updateOnCwdChanged } = useCwdUtils()
// Utils
const { slicePath } = usePathUtils()
return {
networkState,
folders,
currentFolderData,
folderNavigation,
favoriteFolders,
toggleFavorite,
showHiddenFolders,
createFolder,
updateOnCwdChanged,
slicePath
}
}
}
逻辑提取和重用
当涉及跨组件提取和重用逻辑时,Composition API非常灵活地提供了依赖于其参数和全局导入Vue API的功能。
- 跟踪鼠标位置示例
// 导出文件中
import { ref, onMounted, onUnmounted } from 'vue'
export function useMousePosition() {
const x = ref(0)
const y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
// 导入文件中
import { useMousePosition } from './mouse'
export default {
setup() {
const { x, y } = useMousePosition()
// other logic...
return { x, y }
}
}
插件开发
-
Vue插件都将属性注入this。例如,Vue Router注入this. r o u t e 和 t h i s . route和this. route和this.router,Vuex注入this.$store。由于每个插件都要求用户增加注入属性的Vue类型,这使得类型推断变得棘手。
-
使用Composition API时,没有this。相反,插件将充分利用provide并inject在内部和公开的组成功能