Vue3.2 中引入了setup语法糖, 只需要在 script
标签上加上 setup
属性,无需 return
,可以在template模板中直接使用。
data使用
<template>
<h2>{{msg}}</h2>
<h2>{{data.age}}</h2>
<h2>{{age}}</h2>
</template>
<script setup lang="ts">
// 按需引用
import { reactive, ref, toRefs } from "vue";
// ref:定义基本数据类型
const msg = ref('欢迎学习vue3')
// 修改值
msg.value = 'vue3真香'
// reactive:定义引用数据类型
const person = reactive({
name: '花颜',
age: 18
})
// 修改值
person.name = '水门'
// 结构赋值后,模板中可以直接使用
const { age, name } = toRefs(person)
</script>
methods使用
定义函数,直接使用
<template>
<h1>{{msg}}</h1>
<button @click="changeMsg">改变值</button>
</template>
<script setup lang="ts">
import { ref } from "vue";
const msg = ref('欢迎学习vue3')
// 使用箭头函数定义方法(普通函数也可以)
const changeMsg = () => {
msg.value = 'Vue3真香'
}
</script>
ref
vue2中直接使用this.$refs获取真实Dom,vue3中,需要定义一个变量来接受Dom
<template>
<h2 ref="titleRef">我是一个标题</h2>
<button @click="getRef">获取ref</button>
</template>
<script setup lang="ts">
import { ref } from 'vue';
// 定义变量来接收dom
let titleRef = ref(null)
const getRef = ()=>{
// 获取dom节点
console.log(titleRef.value)
console.log(titleRef.value.innerHTML)
}
</script>
computed
定义一个变量来接受computed返回的值
<template>
<h2>{{countText}}</h2>
</template>
<script setup lang="ts">
import { ref,computed } from 'vue';
let count = ref(100)
// computed函数
let countText = computed(()=>{
return `${count.value}%`
})
</script>
watch
<template>
<h2>{{count}}</h2>
<button @click="change">改变值</button>
</template>
<script setup lang="ts">
import { reactive, ref,watch } from 'vue';
let count = ref(100)
let loading = ref(true)
const person = reactive({
name:'Tom',
age:18
})
// 监听基本数据类型
watch(count,(val,oldval)=>{
console.log(val)
})
// 同时监听多个数据
watch([count,loading],(val,oldval)=>{
console.log(val) // [101, false] 返回的值也是数组
})
// 监听引用数据类型
watch(person,(val,oldval)=>{
console.log(val)
})
// 监听对象的某个值
watch(()=>person.name,(val,oldval)=>{
console.log(val)
})
// 同时监听对象多个属性
watch([()=>person.name,()=>person.age],(val,oldval)=>{
console.log(val) // ['Join', 18]
})
const change = ()=>{
loading.value = false
count.value++
person.name = 'Join'
person.age = 18
}
</script>
生命周期
在原来的基础上加上了on来访问组件的生命周期钩子。
vue2 | vue3 |
---|---|
beforeCreate | 去掉(直接在setup中执行) |
created | 去掉(直接在setup中执行) |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
activated | onActivated |
deactivated | onDeactivated |
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from "vue";
onMounted(()=>{
...
})
onBeforeUnmount(()=>{
...
})
</script>
子组件自动导入
父组件引入子组件后,无需注册,可以直接使用
<template>
<Child></Child>
</template>
<script setup lang="ts">
// 直接引入
import Child from './Child.vue'
</script>
插槽
子组件中可以使用useSlots获取到传递过来的插槽对象
- 父组件
<template>
<Son>
<!-- 匿名插槽 -->
<span>默认插槽</span>
<!-- 具名插槽 -->
<template #title>
<h2>具名插槽</h2>
</template>
<!-- 作用域插槽 -->
<template #footer="scope">
<h2>{{scope.msg}}</h2>
</template>
</Son>
</template>
<script setup lang="ts">
import Son from './Son.vue'
</script>
- 子组件
<template>
<!-- 默认插槽 -->
<slot></slot>
<!-- 具名插槽 -->
<slot name="title" ></slot>
<!-- 作用域插槽 -->
<slot name="footer" :msg="msg" ></slot>
<h2>{{$attrs.data}}</h2>
</template>
<script setup lang="ts">
import { ref,useSlots } from 'vue';
let msg = ref('我是子组件中的数据')
// 获取插槽对象
const slots = useSlots()
console.log(slots.default()); //获取到默认插槽的虚拟dom对象
console.log(slots.title()); //获取到具名title插槽的虚拟dom对象
</script>
父子组件通信
需要引入defineProps、defineEmits来声明传递的数据和事件,如果父组件向子组件传递数据,子组件没有使用defineProps接受的话,那么数据就会存放在attrs对象中,模板中使用$attrs接受,script中使用useAttrs接收。
- 父组件
<template>
<Child :msg="msg"
:person="person"
data="非porps传值子组件用$attrs接收"
@changeMsg="changeMsg"></Child>
</template>
<script setup lang="ts">
import { reactive, ref } from "vue";
import Child from "./Child.vue";
const msg = ref('欢迎学习vue3')
const person = reactive({
name: '花颜',
age: 18
})
// 子组件传递过来的事件
const changeMsg = (value) => {
msg.value = value
}
</script>
- 子组件
<template>
<h1>{{msg}}</h1>
<h1>{{person.age}}</h1>
<!-- 没有用props接受的数据放在attrs中 -->
<h1>{{$attrs.age}}</h1>
<button @click="changeMsg">改变msg值</button>
</template>
<script setup lang="ts">
import { defineProps, defineEmits,attrs } from 'vue'
// defineProps:声明props
const props = defineProps({
msg: {
type: String,
default: ""
},
person: {
type: Object,
default: () => { }
},
})
// defineEmits:声明emit事件
const emit = defineEmits(['changeMsg']);
const changeMsg = () => {
emit('changeMsg', 'vue3真不错!')
}
// 使用useAttrs来接受没有用props接受的数据
const attrs = useAttrs()
console.log(attrs.data) // '非porps传值子组件用attrs接收'
</script>
父组件直接获取子组件数据或方法
使用ref获取子组件实例,从而获取属性和方法,需要注意的是,要在子组件中使用defineExpose将对应数据和方法暴露出去,否则父组件无法获取
- 父组件
<template>
<button @click="getSonData">Primary</button>
<Son ref="sonRef"/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Son from './Son.vue'
// 子组件 ref
let sonRef = ref(null)
const getSonData = ()=>{
// 通过ref获取子组件数据
console.log(sonRef.value.msg)
// 通过ref调用子组件方法
sonRef.value.getData()
}
</script>
- 子组件
<script setup lang="ts">
import { ref,defineExpose } from 'vue';
// 数据
let msg = ref('我是子组件中的数据')
// 方法
const getData = ()=>{
console.log('我是子组件中的方法')
}
// 将组件中的属性暴露出去,这样父组件可以获取
defineExpose({
msg,
getData
})
</script>
nextTick
<template>
<h1 ref="titleRef">我是一个标题</h1>
</template>
<script setup lang="ts">
import { ref,nextTick } from 'vue';
let titleRef = ref(null)
nextTick(()=>{
// 获取h1的真实DOM节点
console.log(titleRef.value)
})
</script>
挂载全局属性
main.js中
import { createApp } from 'vue'
import App from './App.vue'
// 创建vue实例
const app = createApp(App)
app.mount('#app')
// 挂载全局属性
app.config.globalProperties.$title = '我是一个全局属性'
文件中使用
<template>
<h1 class="title">{{proxy.$title}}</h1>
</template>
<script setup lang="ts">
import { getCurrentInstance } from 'vue'
// 使用getCurrentInstance获取原型对象
const { proxy } = getCurrentInstance()
// 输出属性
console.log(proxy.$title)
</script>
style v-bind
可以直接在style中书写表达式,使用变量
<template>
<h1 class="title">我是一个标题</h1>
</template>
<script setup lang="ts">
import { ref,nextTick } from 'vue';
let loading = ref(false)
</script>
<style scoped>
.title {
/* 使用v-bind书写表达式 */
color: v-bind("loading?'red':'green'");
}
</style>
mitt.js
Vue2中使用EventBus来进行任意组件之前通信,而Vue3.x 中更推荐使用mitt.js,首先它足够小,仅有200bytes,其次支持全部事件的监听和批量移除,它还不依赖 Vue 实例,所以可以跨框架使用,使用方式也很简单,和EventBus类似。
方式一:在main.js中注册挂载到全局变量上
import { createApp } from 'vue'
import App from './App.vue'
// 导入mitt
import mitt from 'mitt'
// 创建vue实例
const app = createApp(App)
// 挂载mitt到vue原型上
app.config.globalProperties.$mitt = new mitt()
- 父组件
<template>
<button @click="sendMsg">mitt传值</button>
<Son></Son>
</template>
<script setup lang="ts">
import Son from './Son.vue'
import { getCurrentInstance } from 'vue'
// 获取原型对象
let { proxy } = getCurrentInstance()
const sendMsg = () => {
// emit来发送数据
proxy.$mitt.emit('Send', '使用mitt传递过来的值')
}
</script>
- 子组件
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from 'vue';
import { getCurrentInstance } from 'vue'
// 获取原型对象
let { proxy } = getCurrentInstance()
onMounted(() => {
// on来接受数据
proxy.$mitt.on('Send', (msg) => {
console.log(msg)
})
})
onBeforeUnmount(() => {
proxy.$mitt.off('Send')
})
</script>
方式二:创建mitt.js文件
- 新建mitt.js文件
import mitt from 'mitt'
export default new mitt()
- 父组件
<template>
<button @click="sendMsg">mitt传值</button>
<Son></Son>
</template>
<script setup lang="ts">
import Son from './Son.vue'
import mitt from './mitt.js'
const sendMsg = () => {
// emit来发送数据
mitt.emit('Send', '使用mitt传递过来的值')
}
</script>
- 子组件
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from 'vue';
import mitt from './mitt.js'
onMounted(() => {
// on来接受数据
mitt.on('Send', (msg) => {
console.log(msg)
})
})
onBeforeUnmount(() => {
mitt.off('Send')
})
</script>
路由跳转 router
- home页面
<template>
<button @click="goDetail">跳转至detail页面</button>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
// 必须先声明调用
const router = useRouter()
const goDetail = () => {
router.push({
name: 'Detail',
query: {
id: '001'
}
})
}
</script>
- detail页面
<script setup lang="ts">
import { onActivated } from "vue";
import { useRoute } from 'vue-router'
// 必须先声明调用
const route = useRoute()
onActivated(() => {
// 获取路由传递过来的参数
console.log(route.query)
})
</script>
组合式函数——hooks
自定义Hooks,将代码通过功能分块写,响应变量和方法在一起定义和调用,这样后期我们改功能A只需要关注功能A块下的代码,不会像Vue2在Option Api
需要同时关注methos和data。
- 新建hook.js文件
import { ref } from 'vue'
const getUser = () => {
let count = ref(0)
const addCount = () => {
count.value++
}
return {
count,
addCount,
}
}
export default getUser
vue文件中使用
<template>
<h1>{{count}}</h1>
<button @click="goDetail">count+1</button>
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from "vue";
import getUser from './hook.js'
const { count, addCount } = getUser()
const goDetail = () => {
addCount()
}
</script>
provide、inject
provide和inject用于爷孙组件传值
爷组件
<script setup>
import { provide, ref, readonly } from 'vue'
let info = ref('数据在孙组件中不可改变')
let text = ref('数据在孙组件中可以改变')
provide('info', readonly(info))
provide('text', text)
<script>
孙组件
<script setup>
import { inject } from 'vue'
const info = inject('info', '我是info')
const text = inject('text', '我是msg')
</script>
listeners
子组件
<template>
<div>
<el-input v-bind="$attrs"
v-on="$listeners"></el-input>
</div>
</template>
<script setup>
</script>
父组件
<template>
<div>
<myinput v-model="name"
@change="change"></myinput>
</div>
</template>
<script setup>
import { onMounted, provide, ref, readonly } from 'vue'
let name = ref('mark')
const change = ()=>{
}
</script>