Appearance
vue3 组合式 api
概述
传统的Optional API的缺点是,结构性要求是强制的,因为必须要把所有的data写到一起,methods写到一起,computed写到一起
Composition API最大的优点在于,不强制要求结构性,只要保证把data、method、computed暴露出去即可
选项式api
js
export default {
data() {},
methods: {},
computed: {},
watch: {},
props: [],
emits: [],
beforeCreate() {},
created() {},
beforeMount() {},
mounted() {},
beforeUpdate() {},
updated() {},
beforeUnmount() {},
unmounted() {},
beforeRouteEnter() {},
beforeRouteUpdate() {},
beforeRouteLeave() {},
}
快速入门
setup方法
setup也是一种选项式api,但是作为组合式api的入口
即setup内部就要随意编写,使用return语句导出即可
ref
如果导出了一个字面量,只是导出了它的值(值传递),想要做到引用传递,就需要使用ref,并搭配value
vue
<script>
import {ref} from "vue";
export default {
setup() {
// 找到"张三"这一个字符串的引用
// 将这个引用,赋值给name
let name = ref("张三");
let click = () => {
// 让name这一个引用所指向的值,设置为"李四"
name.value = "李四";
}
return {name, click};
}
}
</script>
reactive
如果导出一个对象,那么使用ref是不生效的,不能用传统的(C语言、Java语言)传值、传引用思维去思考。因为传统的思考模式,如果是一个对象,那么就一定是传引用,但是在vue3中不是这样的
如果要导出一个对象,就需要在对象外边,用reactive包裹即可,不需要搭配value就可以使用
vue
<script>
import { reactive } from "vue"
export default {
setup() {
let person = reactive({name: '张三', age: 18})
let click = function() {
person.name = "李四";
}
return {person, click}
}
}
</script>
数组购物车案例
如果数组中的元素是对象类型,那么ref必须用
如果数组中的所有元素都是字面值,ref可用可不用
但为了记忆方便,建议全部使用
vue
<template>
<el-row v-for="item of cart">
<el-col :span="6">
<span>{{ item.name }}</span>
</el-col>
<el-col :span="12">
<el-button @click="decrease(item)">-</el-button>
<el-input v-model="item.count" style="width: 100px"/>
<el-button @click="increase(item)">+</el-button>
</el-col>
</el-row>
</template>
<script>
import {ref} from 'vue'
export default {
setup() {
const cart = ref([{
name: '小米',
count: 1
}, {
name: '小辣椒红辣椒',
count: 1
}])
const decrease = (item) => {
item.count--;
}
const increase = (item) => {
item.count++;
}
return {cart, decrease, increase}
}
}
</script>
数组
如果数组中的所有元素都是字面值,ref可用可不用,但绝对不能用reactive
注意
如果用了ref,注意value的搭配使用
vue
<template>
<ul>
<li v-for="item of menu">{{item}}</li>
</ul>
<input v-model="newItem"/>
<button @click="addItem">添加</button>
</template>
<script>
import {ref} from 'vue'
export default {
setup() {
let menu = ["宫保鸡丁", "鱼香肉丝", "溜肉段"];
let newItem = ref('');
let addItem = () => {
menu.push(newItem.value);
newItem.value = '';
}
return {menu, newItem, addItem};
}
}
</script>
计算属性
vue
<template>
件数: <input type="text" v-model="count"><br/>
单价: <input type="text" v-model="price"><br/>
总价: <input type="text" v-model="total" readonly disabled>
</template>
<script>
import { ref, computed } from 'vue'
export default {
setup() {
const count = ref(1);
const price = ref(9.9);
// 组合式API中computed的用法
// computed是一个函数
// 在这个函数的参数中,再套一层函数
// 内部嵌套的函数的返回值就是计算属性
const total = computed(() => {
return count.value * price.value;
});
return {count, price, total}
}
}
</script>
监听器
vue
<template>
<el-button @click="decrease">-</el-button>
<el-input v-model="count" style="width:100px"/>
<el-button @click="increase">+</el-button>
</template>
<script>
import { ref, watch } from 'vue'
export default {
setup() {
const count = ref(1);
const decrease = function() {
count.value--;
}
const increase = function() {
count.value++;
}
watch(count, (newval, oldval) => {
console.log(newval);
console.log(oldval);
if (newval <= 0) {
count.value = oldval;
}
})
return {count, decrease, increase}
}
}
</script>
生命周期函数
vue
<template>
<el-input v-model="msg"/>
</template>
<script>
import { ref } from 'vue'
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
export default {
setup() {
const msg = ref("hello vue");
console.log("beforeCreate...");
console.log("created...");
onBeforeMount(() => {
console.log("beforeMount...");
})
onMounted(function() {
console.log("mounted...");
})
onBeforeUpdate(function() {
console.log("beforeUpdate...");
})
onUpdated(function() {
console.log("updated...");
})
onBeforeUnmount(function() {
console.log("beforeUnmount...");
})
onUnmounted(function() {
console.log("unmounted...");
})
return {msg};
}
}
</script>
组件通信
props down
setup的第一个参数: props
- 父组件
vue
<template>
<div>
<Child msg="父传子参数"/>
</div>
</template>
<script>
import Child from "@/components/Child.vue";
import { ref } from 'vue'
export default {
components: {Child}
}
</script>
- 子组件
vue
<template>
<div>
<h2>子组件</h2>
<span>{{msg}}</span>
</div>
</template>
<script>
export default {
setup(props, context) {
console.log(props.msg);
},
props: ["msg"]
}
</script>
events up
setup的第二个参数: context
- 父组件
vue
<template>
<Child2 @send="receive"/>
</template>
<script>
import Child2 from "@/components/Child2.vue";
export default {
setup() {
const receive = function(msg) {
console.log("父组件接到了消息:");
console.log(msg);
}
return { receive };
},
components: {Child2}
}
</script>
- 子组件
vue
<template>
<div>
<button @click="send">向父组件传消息</button>
</div>
</template>
<script>
export default {
setup(props, context) {
const send = function() {
// 选项式api:
// this.$emit("send", "参数")
// 组合式api
context.emit("send", "子组件传来的消息")
}
return {send}
}
}
</script>
ref通信
vue
<template>
<el-tag type="success" ref="tag">tag</el-tag>
<button @click="getTagProp">获取tag里的属性</button>
</template>
<script>
import {ref} from 'vue'
export default {
setup() {
// 注意,js中的引用名,必须和组件中的ref名一模一样
// 页面加载过程
// 第一步,先执行setup里边的内容,再把tag作为return暴露出去
// 第二步,渲染template标签
// 第三步,在渲染的过程中,发现了某一个组件的ref为tag,且setup中导出了一个tag,且这个tag定义是使用了ref方法,且没写ref方法的参数
// 所以将组件绑定到tag变量上
const tag = ref();
const getTagProp = function() {
console.log(tag.value.type);
}
return {getTagProp, tag}
}
}
</script>
多级组件通信
- 第一级组件
js
import { provide } from "vue";
// 发msg,msg为hello
provide('msg','hello');
- 第三级组件
js
import {inject} from 'vue'
// 收msg
console.log(inject('msg'));
总线通信
Vue3官方推荐mitt或tiny-emitter
在组合式api中,tiny-emitter会方便一点
mitt
安装
bashpnpm i mitt
封装
- bus/index.js
jsimport mitt from 'mitt' const bus = mitt() export default bus;
消息发布
vue<script> import bus from '@/bus' export default { setup() { bus.emit("event", "Hello!"); } } </script>
消息订阅
vue<script> import bus from '@/bus' export default { setup() { bus.on("event", (msg) => { console.log(msg); }) } } </script>
tiny-emitter
安装
bashpnpm i tiny-emitter
消息发布
vue<script> import bus from 'tiny-emitter/instance' export default { setup() { bus.emit("event", "Hello"); } } </script>
消息订阅
vue<script> import bus from 'tiny-emitter/instance' export default { setup() { bus.on("event", msg => console.log(msg)); } } </script>
局部守卫
注意
因为 setup 的执行时机在 routeEnter 以后
所以在组合式api中并不支持 routeEnter
需借助选项式api
vue
<script>
import { onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'
export default {
setup() {
onBeforeRouteUpdate(() => {
console.log("route update...");
})
onBeforeRouteLeave(() => {
console.log("route leave...");
})
}
}
</script>
编程式路由
源组件
vue
<script>
import { useRouter } from 'vue-router'
export default {
setup() {
const router = useRouter();
const jump = function() {
router.push({path: "/jump-to", query: {movieId: 210}});
}
return {jump}
}
}
</script>
目标组件
vue
<script>
import { useRoute } from 'vue-router'
export default {
setup() {
const route = useRoute();
console.log(route.query.movieId);
}
}
</script>
vuex
vue
<template>
<div>{{$store.state.flag}}</div>
<div>{{$store.getters.getFlag}}</div>
<button @click="setFlag">点击将vuex中的flag设置为true</button>
<button @click="queryFlag">点击触发vuex中的queryFlag action</button>
</template>
<script>
import { useStore } from 'vuex'
export default {
setup() {
// setup时机,等于beforeCreate或created
const store = useStore();
console.log(store.state.flag);
console.log(store.getters.getFlag);
const setFlag = function() {
store.commit("setFlag", true);
}
const queryFlag = function() {
store.dispatch("queryFlag");
}
return {setFlag, queryFlag}
}
}
</script>
<script setup>
语法糖
优势
- 自动导出,无需
return
- 自动组件注册,无需编写
components
选项
劣势
- 不支持
setup
选项中的props
与context
参数,可使用defineProps
或defineEmits
解决 - 不支持
beforeRouteEnter
选项,可叠加声明script
标签解决
defineProps
vue
<script setup>
// 无需import
const props = defineProps({
foo: String
})
</script>
defineEmits
vue
<script setup>
const emit = defineEmits(['send'])
emit('send', 'param');
</script>
this
关键字解决方案
在 组合式API
中 this
是 undefined
,vue3提供了getCurrentInstance()
方法用于获取当前组件实例。
vue
<script setup>
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
</script>