Composition API
setup
setup 是 Vue3.x 新增的一个选项, 他是组件内使用 Composition API的入口。
setup 参数
使用setup时,它接受两个参数:
- props: 组件传入的属性
- context
setup 中接受的props是响应式的, 当传入新的 props 时,会及时被更新。由于是响应式的, 所以不可以使用 ES6 解构,解构会消除它的响应式。 错误代码示例, 这段代码会让 props 不再支持响应式:
// demo.vue
export default defineComponent ({
setup(props, context) {
const { name } = props
console.log(name)
},
})
在开发中我们想要使用解构,还能保持props的响应式,可以采用toRefs。 setup接受的第二个参数context,context中就提供了this中最常用的三个属性:attrs、slot 和emit,分别对应 Vue2.x 中的 $attr属性、slot插槽 和$emit发射事件。并且这几个属性都是自动同步最新的值,所以我们每次使用拿到的都是最新值。
响应式数据
- ref:可传入任意类型的值并返回一个响应式且可改变的ref对象。ref对象拥有一个指向内部值的单一属性
.value,改变值的时候必须使用其value属性 - reactive:接受一个普通对象然后返回该普通对象的响应式代理。等同于2.x的
Vue.obserable()
简写之:reactive负责复杂数据结构,ref可以把基本的数据结构包装成响应式
reactive
<template>
<div>
<h2>{{state.count}}</h2>
<button @click="add">计算</button>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
setup(){
// 响应式变量声明 reactive负责复杂数据结构,
const state = reactive({
count: 1
});
function add() {
state.count++;
}
return { state, add};
}
};
</script>
ref
<template>
<div>
<h2>{{state.count}}</h2>
<h3>{{num}}</h3>
<button @click="add">计算</button>
</div>
</template>
<script>
import { reactive, ref } from "vue";
export default {
setup(){
const state = reactive({
count: 1
});
const num = ref(0);
function add() {
state.count++;
num.value+=2
}
return { state, add, num };
}
};
</script>
toRef 和 toRefs
创建一个 ref 对象,其value值指向另一个对象中的某个属性。
语法:const name = toRef(person,'name')
应用: 要将响应式对象中的某个属性单独提供给外部使用时。
扩展:toRefs 与toRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
<template>
<div>
<h1>姓名:{{person.name}}</h1>
<h2>年龄:{{person.age}}</h2>
<h3>喜欢的水果:{{person.likeFood.fruits.apple}}</h3>
</div>
</template>
<script>
import {reactive} from 'vue'
export default {
name: "App",
setup() {
// 定义了一段数据
let person = reactive({
name: '张三',
age: 18,
likeFood: {
fruits:{
apple: '苹果'
}
}
})
// 将数据返回出去
return {
person,
}
}
};
</script>
我们可以看到在模板中的使用的数据 都是person.xxx 、person.xxx。 person一直出现在插值语法中看起来并不整洁
使用toRef
首先引入toRef方法 然后在返回的时候调用toRef并传入要返回的对象里面的哪个值,就可以实现响应式数据了
// 引入toRef方法
import {reactive, toRef} from 'vue'
// 返回的时候调用toRef方法将要返回的对象和属性传递给toRef方法
return {
name: toRef(person, "name"),
age: toRef(person, "age"),
apple: toRef(person.likeFood.fruits, "apple")
}
这样就可以实现数据响应了
使用toRefs
引入toRefs方法 我们还可以使用toRefs来包装多个ref数据 解构之后返回 模板中也可以使用
<template>
<div>
<h1>姓名:{{name}}</h1>
<h2>年龄:{{age}}</h2>
<h3>喜欢的水果:{{likeFood.fruits.apple}}</h3>
</div>
</template>
...
return {
...toRefs(person) // 将对象解构后返回
}
...
computed
传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象。
import { reactive, ref, computed } from "vue";
export default {
setup() {
// 1.响应式变量声明 reactive负责复杂数据结构,
const state = reactive({
count: 1
});
// 2.ref可以把基本的数据结构包装成响应式
const num = ref(0);
// 3.创建只读的计算属性
const computedEven1 = computed(() => state.count % 2);
// 4.创建可读可写的计算属性
const computedEven2 = computed({
get:()=>{
return state.count % 2;
},
set: newVal=>{
state.count = newVal;
}
})
// 事件的声明
function add() {
state.count++;
num.value += 2;
}
function handleClick() {
computedEven2.value = 10;
}
return { state, add, num, computedEven1,computedEven2,handleClick };
}
};
watchEffect
立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。
const num = ref(0)
watchEffect(() => console.log(count.value))
// -> 打印出 0
setTimeout(() => {
count.value++
// -> 打印出 1
}, 100)
1.停止监听
隐式停止
当 watchEffect 在组件的 setup() 函数或生命周期钩子被调用时, 侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止
显示停止
在一些情况下,也可以显示调用返回值来停止侦听
const stop = watchEffect(()=>{
/*...*/
})
//停止侦听
stop()
2.清除副作用
有时候副作用函数会执行一些异步的副作用,这些响应需要在其失效时来清除(即完成之前状态已改变了)。可以在侦听副作用传入的函数中接受一个onInvalidate函数作为参数,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:
- 副作用即将重新执行时
- 侦听器被停止(如果在
setup()或生命周期钩子函数中使用了watchEffect,则在卸载组件时)
官网的例子:
watchEffect((onInvalidate) => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
// id 改变时 或 停止侦听时
// 取消之前的异步操作
token.cancel()
})
})
生命周期钩子
Vue3 通信使用写法
1. props
// Parent.vue 传送
<child :msg1="msg1" :msg2="msg2"></child>
<script>
import child from "./child.vue"
import { ref, reactive } from "vue"
export default {
data(){
return {
msg1:"这是传级子组件的信息1"
}
},
setup(){
// 创建一个响应式数据
// 写法一 适用于基础类型 ref 还有其他用处,下面章节有介绍
const msg2 = ref("这是传级子组件的信息2")
// 写法二 适用于复杂类型,如数组、对象
const msg2 = reactive(["这是传级子组件的信息2"])
return {
msg2
}
}
}
</script>
// Child.vue 接收
<script>
export default {
props: ["msg1", "msg2"],// 如果这行不写,下面就接收不到
setup(props) {
console.log(props) // { msg1:"这是传给子组件的信息1", msg2:"这是传给子组件的信息2" }
},
}
</script>
2. $emit
// Child.vue 派发
<template>
// 写法一
<button @click="emit('myClick')">按钮</buttom>
// 写法二
<button @click="handleClick">按钮</buttom>
</template>
<script setup>
// 适用于Vue3.2版本 不需要引入
// import { defineEmits } from "vue"
// 对应写法一
const emit = defineEmits(["myClick","myClick2"])
// 对应写法二
const handleClick = ()=>{
emit("myClick", "这是发送给父组件的信息")
}
</script>
// Parent.vue 响应
<template>
<child @myClick="onMyClick"></child>
</template>
<script setup>
import child from "./child.vue"
const onMyClick = (msg) => {
console.log(msg) // 这是父组件收到的信息
}
</script>
3. expose / ref
父组件获取子组件的属性或者调用子组件方法
<script setup>
// 适用于Vue3.2版本, 不需要引入
// import { defineExpose } from "vue"
defineExpose({
childName: "这是子组件的属性",
someMethod(){
console.log("这是子组件的方法")
}
})
</script>
// Parent.vue 注意 ref="comp"
<template>
<child ref="comp"></child>
<button @click="handlerClick">按钮</button>
</template>
<script setup>
import child from "./child.vue"
import { ref } from "vue"
const comp = ref(null)
const handlerClick = () => {
console.log(comp.value.childName) // 获取子组件对外暴露的属性
comp.value.someMethod() // 调用子组件对外暴露的方法
}
</script>
4. attrs
attrs:包含父作用域里除 class 和 style 除外的非 props 属性集合
// Parent.vue 传送
<child :msg1="msg1" :msg2="msg2" title="3333"></child>
<script setup>
import child from "./child.vue"
import { ref, reactive } from "vue"
const msg1 = ref("1111")
const msg2 = ref("2222")
</script>
// Child.vue 接收
<script setup>
import { defineProps, useContext, useAttrs } from "vue"
// Vue3.2版本不需要引入 defineProps,直接用
const props = defineProps({
msg1: String
})
const attrs = useAttrs()
console.log(attrs) // { msg2:"2222", title: "3333" }
</script>
5. v-model
可以支持多个数据双向绑定
<child v-model:key="key" v-model:value="value"></child>
<script setup>
import child from "./child.vue"
import { ref, reactive } from "vue"
const key = ref("1111")
const value = ref("2222")
</script>
// Child.vue
<template>
<button @click="handlerClick">按钮</button>
</template>
<script setup>
// Vue3.2版本,不需要引入
// import { defineEmits } from "vue"
const emit = defineEmits(["key","value"])
// 用法
const handlerClick = () => {
emit("update:key", "新的key")
emit("update:value", "新的value")
}
</script>
6. provide / inject
provide / inject 为依赖注入
provide:可以让我们指定想要提供给后代组件的数据
inject:在任何后代组件中接收想要添加在这个组件上的数据,不管组件嵌套多深都可以直接拿来用
// Parent.vue
<script setup>
import { provide } from "vue"
provide("name", "沐华")
</script>
// Child.vue
<script setup>
import { inject } from "vue"
const name = inject("name")
console.log(name) // 沐华
</script>
7. Vuex
// store/index.js
import { createStore } from "vuex"
export default createStore({
state:{ count: 1 },
getters:{
getCount: state => state.count
},
mutations:{
add(state){
state.count++
}
}
})
// main.js
import { createApp } from "vue"
import App from "./App.vue"
import store from "./store"
createApp(App).use(store).mount("#app")
// Page.vue
// 方法一 直接使用
<template>
<div>{{ $store.state.count }}</div>
<button @click="$store.commit('add')">按钮</button>
</template>
// 方法二 获取
<script setup>
import { useStore, computed } from "vuex"
const store = useStore()
console.log(store.state.count) // 1
const count = computed(()=>store.state.count) // 响应式,会随着vuex数据改变而改变
console.log(count) // 1
</script>
8. mitt
Vue3 中没有了 EventBus 跨组件通信,但是现在有了一个替代的方案 mitt.js,原理还是 EventBus
先安装 npm i mitt -S
然后像以前封装 bus 一样,封装一下
mitt.js
import mitt from 'mitt'
const mitt = mitt()
export default mitt
然后两个组件之间通信的使用
// 组件 A
<script setup>
import mitt from './mitt'
const handleClick = () => {
mitt.emit('handleChange')
}
</script>
// 组件 B
<script setup>
import mitt from './mitt'
import { onUnmounted } from 'vue'
const someMethed = () => { ... }
mitt.on('handleChange',someMethed)
onUnmounted(()=>{
mitt.off('handleChange',someMethed)
})
</script>