<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>
示例来源
经验所得,Vue当前API会带来以下两类编程模型的限制:
所以为在组织代码时给用户提供更大的灵活性,有以下目标:
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}`
})
// 要直接创建一个计算值,我们可以使用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
保持代码井井有条的最终目的应该是使代码更易于阅读和理解。
“理解”代码,在于理解它运用数据、逻辑,最终解决的逻辑功能问题。
示例
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在内部和公开的组成功能
参考:插件开发
APIReference