vue3刚出测试版的时候尝过一次,后来学了react,才尝出点味道来,现在再尝一遍,先从重要的compositon api入手!
composition api 主要是把之前vue的核心api暴露出来,让用户可以更加理解vue的内部原理,同时也有助于梳理代码逻辑和代码结构
Reactive
Reactive 其实和2.x的Vue.observable()是等价的
import { reactive } from 'vue'
// reactive state
const state = reactive({
count: 0
})
这个api最重要的使用场景是在渲染期间使用,因为有依赖追踪,当响应式的state的值发生变化的时候,视图就会自动更新,在DOM中渲染某些东西通常是被视做“有副作用的”,为了根据reactive state来应用并自动应用副作用,我们可以使用watchEffect来实现
import { reactive, watchEffect } from 'vue'
const state = reactive({
count: 0
})
watchEffect(() => {
document.body.innerHTML = `count is ${state.count}`
})
watchEffect 会立即执行该函数,并将执行过程中用到的所有的响应式状态的属性作为依赖进行追踪
! 注:这里不像react 需要我们自动添加依赖
这里面的state.count首次执行后作为依赖被追踪,当它的值未来发生变化的时候,这个函数就会重新执行
这其实就是Vue响应式的精髓,在2.x的时候,我们把属性绑定在data()的返回值上,使其变为响应式,其内部的原理其实就是如此
我们下面来看另一个和响应式有关的api
ref
这个ref其实对应着我们2.x的计算属性,也就是computed,通常我们在依赖另一个状态的值的时候会用到计算属性,也就是我们通过watchEffect来追踪依赖,当值变化的时候进行返回,那么对于基本属性,js是值传递的,一旦返回我们就无法追踪,所以我们可以利用一个对象,把这个值包裹到一个对象中然后再返回,当然我们也得拦截这个对象,监听他的读写来实现依赖追踪
以上原理其实也就是refapi的实现原理,但我们为此付出的代价就是每次都要通过.value来获取最新的值
const double = computed(() => state.count * 2)
watchEffect(() => {
console.log(double.value)
}) // -> 0
state.count++ // -> 2
这里double就是ref,既然ref是一个api,我们可以简写成这样
const count = ref(0)
console.log(count.value) // 0
count.value++
//count.value-->1
! 2.x中,我们已经有了ref的概念了,只是为了获取dom实例,但新的ref系统可以同时用于逻辑状态和模版引用
解开ref
如果每次使用都得带上.value,那么未免给我们编程的时候加上了一层心智负担,所以在一些场景是会自动解开ref的,也就是Vue内部会自动使用.value,我们只用写变量名字就可以啦
比如在模版中,我们不必写成{{count.value}},直接{{count}}完事
还有在ref值嵌套于reactive之中时,也会自动解开
const state = reactive({
count: 0,
double: computed(() => state.count * 2)
});
// 无需再使用 `state.double.value`
console.log(state.double)
我们了解了这两个响应式api后,我们需要让他们在组件中生效,那么我们还需要一个工具 setup函数
<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>
我们把代码逻辑写在setup函数中,然后把模版用到的部分返回即可,无论是方法还是变量
现在我们的组件仅仅需要一个模版 一个setup函数即可构建一个单文件组件,而且这种函数式编程还可以给我们提供更好的ts支持,让编程体验进一步提升~
然后另一个需要我们关注的问题就是———— 生命周期的钩子函数如何使用?
生命周期可太重要了,我们需要知道我们在什么时候可以做哪些事情,例如控制台打印属性,发送ajax请求等(这些也都是副作用)
在vue3中,我们可以使用形如onXXX的API(对应现有的生命周期选项)
import { onMounted } from 'vue'
export default {
setup() {
onMounted(() => {
console.log('component is mounted!')
})
},
}
这些生命周期注册方法只能用在 setup 钩子中。它会通过内部的全局状态自动找到调用此 setup 钩子的实例。有意如此设计是为了减少将逻辑提取到外部函数时的冲突。
与现有API配合
另一个我们比较关注的问题就是: 这个组合式API能不能和我们现有的项目兼容? 答案是肯定的,注意一下两点
-
组合式 API 会在 2.x 的选项 (data、computed 和 methods) 之前解析,并且不能提前访问这些选项中定义的 property。
-
setup() 函数返回的 property 将会被暴露给 this。它们在 2.x 的选项中可以访问到。
我们结合一个例子来看一下
我们可以看到在setup中调用data、methods里的属性和方法会报错,因为组合式api解析在前,另外组合式api里返回的属性会自动暴露给this,我们按照之前的习惯正常调用即可。
Ref vs Reactive
我们看一下官方的解释
// 风格 1: 将变量分离
let x = 0
let y = 0
function updatePosition(e) {
x = e.pageX
y = e.pageY
}
// --- 与下面的相比较 ---
// 风格 2: 单个对象
const pos = {
x: 0,
y: 0,
}
function updatePosition(e) {
pos.x = e.pageX
pos.y = e.pageY
}
-
如果使用 ref,我们实际上就是将风格 (1) 转换为使用 ref (为了让基础类型值具有响应性) 的更细致的写法。
-
使用 reactive 和风格 (2) 一致。我们只需要通过 reactive 创建这个对象。
总结起来就是ref就是让基础类型的值具有响应性,reactive就是让一个对象具有响应性,
但是reactive有一个弊端就是用它创建的对象不能被解构,否则会丢失响应性,
解决办法就是通过toRefs这个API将其每个属性变为响应式
function useMousePosition() {
const pos = reactive({
x: 0,
y: 0,
})
// ...
return toRefs(pos)
}
// x & y 现在是 ref 形式了!
const { x, y } = useMousePosition()
与React Hooks比较
还记得本人在学习hooks之时,立马就回想起了组合式API,因为hooks也就是组合式api的灵感来源,这里我们只关注不同点,其他的见仁见智,我主要摘出我觉得比较不同的几点
- 组合式 API 的 setup() 函数只会被调用一次,不会在每次渲染时重复执行,以降低垃圾回收的压力;
- 不存在忘记记录依赖的问题,也不需要“useEffect”和“useMemo”并传入依赖数组以捕获过时的变量。Vue 的自动依赖跟踪可以确保侦听器和计算值总是准确无误。
vue很好的结合了他的响应式模型,可以说强化了hooks时的一些编程体验
定位
组合式API被定位一个高级特性,这意味着他并不是vue3的默认解决方案,你也可以继续选择2.x,但他会给你带来不一样的编程体验,它解决的问题主要出现在一些大型应用程序中,这完全取决于你自己。
总结
我们大概梳理一下组合式API的心智模型:
-
单文件组件的格式可以 仅有 模版和setup函数构成
-
setup函数作为文件的入口,通过执行组合函数来实现相应逻辑,最后通过return 返回模版需要的变量或方法
-
每个组合函数都是可拆分的、可复用的、独立的,我们不仅可以在当前组件中声明,还可以把他们放在一个单独的文件中来被其他组件引用
-
在每个组合函数中,我们可以应用vue特有的响应式模型,通过ref、reactive这两个API来将一些变量变成响应式
-
setup中可以使用一些生命周期钩子函数来执行指定的副作用