Vue3 的新特性 —— Composition-Api
作者:旋目
前言
随着Vue3.0正式的发布,正如你所期望的那样,Vue3.0带来了很多令人兴奋的新功能。今天我们就来探究一下新特性(Composition-Api)。
什么是Composition API
Vue团队主要是在Vue2.x的API的基础上引入了一些补充和升级,对一些API进行了替换升级和重命名,就这样组成了Vue3.0+Composition API这种全新的逻辑重用和代码组织方法。我们可以叫他合成函数或者集成API。
Composition API的出现解决了什么
我们来看一下Vue2.x一个大型组件的示例:
其中逻辑关注点是按颜色分组,由methods,computed,watch,data中等等定义属性和方法,共同处理页面逻辑。
项目小还好,清晰明了,但是项目大了后,一个methods中可能包含20多个方法,你往往分不清哪个方法对应着哪个功能。这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。
我们再看一下Vue3.0的结构,一个功能所定义的所有api会放在一起:
这里我们可以看到不同颜色的代码片段由composition-api 把复杂组件的逻辑编写的更紧凑,而且可以将公共逻辑进行抽取。
下面举例看下如何抽取分离代码:
count.js
import { ref } from 'vue'
function useCount() {
const count = ref(0)
function addCount () {
count.value++
console.error(count.value)
}
return {
count,
addCount
}
}
export default useCount;
复制代码
上面的函数我们定义了变量count,然后声明一个addCount函数,以控制count数量的累加,我们继续看调用:
<template>
<view @click="addCount">
count:{{state.count}}
</view>
</template>
import useCount from './count.js'
<script lang='ts'>
import {defineComponent} from "vue";
import useCount from './count.js'
export default defineComponent ({
setup() {
const { count, addCount } = useCount()
return {
count,
addCount
};
}
});
</script>
复制代码
实现点击效果:
上面将useCount方法注入后便可以实现调用,useCount暴露出来的count变量、addcount方法可以供所有有关联性业务需要的组件调用。
Composition API的语法糖
Composition API提供了以下几个函数式的 API
- ref
- reactive
- toRefs
- computed
- watchEffect
- watch
- readonly()
ref()
ref() 函数可以根据给定的值来创建一个响应式的数据对象,返回值是一个对象,且只包含一个 .value 属性。
<template>
<view @click="addCount">
count:{{state}}
</view>
</template>
<script lang='ts'>
import { defineComponent, ref, toRefs } from "vue";
export default defineComponent ({
setup() {
let state = ref(0); // 建立一个响应式对象
function addCount() {
state.value++; //使用ref创建的变量需要通过.value访问
}
return {
state,
addCount
};
}
});
</script>
复制代码
我们可以看到在DOM中直接渲染就可以展示state的值,这里需要注意的是在dom中可以直接使用state但是在函数体中需要使用state.value来进行修改(因为ref返回的是一个对象)
实现点击效果:
reactive()
reactive() 函数接收一个普通的对象,返回出一个响应式对象。
在Vue2.x的版本中,我们只需要在 data() 中定义一个数据就能将它变为响应式数据,在 Vue3.0 中,需要用 reactive 函数或者 ref 来创建响应式数据
<template>
<view @click="addCount">
count1:{{state2}}
<br />
count2:{{state.count}}
</view>
</template>
<script lang='ts'>
import {defineComponent, reactive, ref } from "vue";
export default defineComponent({
setup() {
const state = reactive({ // 建立一个响应式对象
count: 0
});
let state2 = ref(0);
function addCount() {
state.count++; //使用reactive创建的对象需要通过state.count来访问
state2.value++;
}
return {
state,
state2,
addCount
};
}
});
</script>
复制代码
实现点击效果:
这里我们可以看到reactive跟ref创建的count都变化了。可能这里会有人问,既然实现结果都一样,那这两个API我随便用?下面来看一下reactive() + toRefs()
toRefs()
看一下官方介绍toRefs():将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的ref。
这里我们使用了…toRefs(state),这个API可以把reactive的值处理为ref(),分解后返回的是一个普通对象,但是它还保存着数据的响应性。
这里可以看出来
- ref 函数传入一个值作为参数,一般传入基本数据类型(类似于react-hooks中useState),返回一个基于该值的响应式Ref对象,该对象中的值一旦被改变和访问,都会被跟踪到,就像我们改写后的示例代码一样,通过修改 count.value 的值,可以触发模板的重新渲染,显示最新的值。
- reactive是用来定义更加复杂的数据类型,但是定义后里面的变量取出来就不在是响应式Ref对象数据了。
computed()
computed()计算属性,返回值返回一个ref对象,有2种写法。
1.创建只读计算属性
const count = ref(1)
// 创建一个计算属性,使其值比 count 大 1
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 输出 2
plusOne.value++ // error 不可写
复制代码
修改count,plusOne会跟着自动+1,但如果修改nextAge,会有警告:计算属性不能修改
2.创建可读可写计算属性
const count = ref(1)
const plusOne = computed({
//取值
get: () => count.value + 1,
set: val => {//赋值
count.value = val - 1
}
})
// 给计算属性赋值,会触发 set 函数
plusOne.value = 1
// set后 count.value的值会更新
console.log(count.value) // 0
复制代码
Vue3.0中计算属性的函数中如果只传入一个回调函数,表示的是get,如果传入一个对象,表示的是get和set。
watchEffect()
watchEffect()是在响应式地跟踪其依赖项时立即运行一个函数,并在更改依赖项时重新运行它。
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> 打印结果 0
setTimeout(() => {
count.value++
//打印结果 1
}, 100)
复制代码
执行结果
第一次打印的是0,当通过定时器触发值的修改时,触发函数打印1。它比较与watch使用起来更为便捷,每次初始化时会执行一次回调函数来自动获取依赖,没有惰性。
watch()
watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调。
监听一个ref值
<template>
<view @click="addCount">
count:{{state}}
</view>
</template>
<script>
import { reactive, watch } from 'vue'
export default {
setup () {
const count = ref(0)
const addCount = () => state.count++;
watch(count, (count, prevCount) => {
console.log(count,prevCount) //改变触发回调返回新值旧值
})
return { state, addCount }
}
}
</script>
复制代码
监听一个对象
<template>
<view @click="addCount">
count:{{state}}
</view>
</template>
<script>
import { reactive, watch } from 'vue'
export default {
setup () {
const state = reactive({
count: 0
})
const addCount = () => state.count++;
watch(
() => state.count, //可以监听一个对象也可以是内部某个值
(count, prevCount) => {
console.log(count,prevCount) //改变触发回调返回新值旧值
}
)
return { state, addCount }
}
}
</script>
复制代码
侦听多个源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
console.log(foo,bar,prevFoo,prevBar) //改变触发回调
})
复制代码
与 watchEffect相比:
- 访问侦听状态的先前值和当前值。
- 可以侦听多个数据的变化。
- watch存在惰性第一次进来不会执行,值变了才会执行,watchEffect没有惰性,进页面便会加载,只有涉及到相关 的依赖变化了就会执行
readonly()
readonly()传入一个响应式对象、普通对象或 ref ,返回一个只读的对象代理。这个代理是深层次的,对象内部的数据也是只读的。
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 适用于响应性追踪
console.log(copy.count)
})
/// original 上的修改会触发 copy 上的侦听
original.count++
// 只读属性是不能修改的
copy.count++ // 警告!
复制代码
小结
说到这里我们对Vue3.0的Composition-Api有了一个大致的了解,它标志性的展示了集成函数的灵活,支持的复杂性更高,希望大家看了上文能更好地了解Composition-Api的带来的变化。