为什么要使用Vue3
痛点解决
- Vue3通过使用Proxy的劫持对象属性的方式来实现响应式,解决Vue2的Object.defineProperty方法中对于直接修改数组和对象属性值不能监听到响应式的痛点。
- Vue3通过使用Composition API方式,使组件的逻辑更加灵活和可复用提高代码的可维护性和可读性。解决Vue2中定义式写法的死板代码臃肿的痛点。
- Vue3通过使用Hooks方式,使其代码隔离。解决Vue2中通过mixins方式造成组件中代码污染的痛点。
- Vue3通过TypeScript进行了全面优化,通过类型推断提供更强大的类型检查,能够更早地发现潜在的错误和对象属性的定义,解决Vue2中写法问题的纠正和对于对象属性不方便查看的痛点。
性能提升
- Vue 3引入了虚拟DOM的重写和编译器的重构,大大提升了性能。新的虚拟DOM比旧版本更轻量,渲染速度更快。编译器的重构则使得编译代码的速度大幅提升,减少了模板解析的时间。
- Vue3在包的体积上也进行了优化。通过使用Tree-shaking和代码拆分等技术,使得Vue3的核心库的体积更小,减少了应用的加载时间,提升了用户体验。
- Vue3结合vite实现了局部更新的特性,大大减少了开发编译时的时间。
迭代更新和竞争
- Vue 2 的官方维护和支持将在2023年1月31日结束。这意味着在这个日期之后,Vue 2的官方团队不再提供修复、新功能更新或维护支持。
- 新的项目基本都会采用Vue3技术,在找工作时也是目前必备的技能之一。
语法对比
main.js
- Vue2
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
// 采用 use方法进行全局组件的注册
Vue.use(ElementUI);
// 通过引入Vue对象并new创建
new Vue({
render: h => h(App),
}).$mount('#app')
- Vue3
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// 可以链式调用use
createApp(App).use(ElementPlus).mount('#app')
// 在vite.config中直接注入vue(vite.config.js)
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
})
Vue3完全兼容Vue2写法
- 解决Vue2中defineProperty方法中对于直接修改数组和对象属性值不能监听到响应式的痛点
Vue2
<template>
<div>
<span>姓名:{{ people.name }}</span>
<span>年龄:{{ people.age || '--' }}</span>
<span>成绩:{{ grade[0] }}</span>
<el-button @click="changeInfo">更改信息</el-button>
</div>
</template>
<script>
export default {
data(){
return {
people:{
name:'小明'
},
grade:[67,90,56],
}
},
methods:{
// vue2错误方式
changeInfo(){
this.people.age = 16 // 未初始化的属性
this.grade[0] = 89 // 直接通过索引修改数组信息
},
// vue2正确方式
changeInfo(){
this.$set(this.people,'age',16)
// 方法一利于vue2封装的数组方法
this.grade.splice(0, 1 , 89)
// // 方法二利用$set()
this.$set(this.grade, 0 ,89)
},
}
}
</script>
Vue3
<template>
<div>
<span>姓名:{{ people.name }}</span>
<span>年龄:{{ people.age || '--' }}</span>
<span>成绩:{{ grade[0] }}</span>
<el-button @click="changeInfo">更改信息</el-button>
</div>
</template>
<script>
export default {
data(){
return {
people:{
name:'小明'
},
grade:[67,90,56],
}
},
methods:{
changeInfo(){
this.people.age = 16 // 未初始化的属性,直接修改也具有响应式
this.grade[0] = 89 // 直接通过索引修改数组信息也具有响应式
},
}
}
</script>
template 模板
Vue2
// 仅支持一个根节点
// 错误写法
<template>
<span>姓名</span>
<span>年龄</span>
<span>成绩</span>
<el-button>更改信息</el-button>
</template>
// 正确写法
<template>
<div>
<span>姓名</span>
<span>年龄</span>
<span>成绩</span>
<el-button>更改信息</el-button>
</div>
</template>
Vue3
// 支持多个节点
<template>
<span>姓名</span>
<span>年龄</span>
<span>成绩</span>
<el-button>更改信息</el-button>
</template>
methods
- vue2 methods中可以用this直接访问数据修改,vue3中则没有this要按照正常的函数写法
Vue2
<template>
<div>
<el-button @click="changeColor">更改</el-button>
</div>
</template>
<script>
export default {
data(){
return {
info:{
name:'小米',
age:10
}
}
},
methods:{
changeColor(){
this.info.name = 200
}
}
}
Vue3
<template>
<div>
</div>
<el-button @click="changeValue">更改</el-button>
</template>
<script setup>
import { ref } from 'vue'
const data = ref('红色')
// 更改数据
const changeValue = () => {
data.value = '黑色'
}
</script>
data
Vue2
<script>
export default {
data(){
return {
people:{
name:'小明'
},
grade:[67,90,56],
}
},
}
</script>
Vue3
ref() 定义响应式数据
- ref()函数一般实现基本数据类型的响应式数据。
- ref()支持所有数据类型的响应式创建。
- ref()加工出的数据是一个响应式对象数据,并存放在该对象的value下,所以在js中要修改数据需要对value属性进行赋值操作
- 模板中会检查是否是ref()生成的响应式数据,如果是模板则会默认进行读取value属性的操作,所以模板中不用进行 .value的读取操作
<template>
<div>
{{ count2 }}
{{ obj2.name }}
</div>
<el-button @click="changeValue">更改数据</el-button>
</template>
<script setup>
import { ref} from 'vue'
// ref可以定义基本数据类型和引用数据类型
const count2 = ref(20)
const obj2 = ref({
name: 'default2',
age: 21
})
// 修改数据
const changeValue = () => {
obj2.value.name = 'default3'
count2.value = 30
}
</script>
reactive() 定义响应式数据
- reactive()只能创建引用数据类型的响应式【Object】,非引用数据类型修改值检测不到改值的响应变化
- 当定义引用数据类型响应式数据时,推荐使用 reactive(),reactive 创建出来的是一个干净的Proxy代理对象可以省去取值.value的操作
<template>
<div>
{{ obj.name }}
</div>
<el-button @click="changeValue">更改数据</el-button>
</template>
<script setup>
import { reactive} from 'vue'
const obj = reactive({
name: 'John'
})
console.log(obj)
const changeValue = () => {
obj.name += 'Jane'
}
</script>
computed
Vue2
<template>
<div>
{{ name }}
{{ color }}
<el-button @click="changeColor">更改</el-button>
</div>
</template>
<script>
export default {
data(){
return {
colorList:['红','黄']
}
},
computed:{
// 写法一
name(){
return '小米'
},
// 写法二
color:{
get(){
return this.colorList[0]
},
set(value){
this.colorList.splice(0,1,value)
}
}
},
methods:{
changeColor(){
this.color = '其他'
}
}
}
</script>
Vue3
<template>
<div>
{{ color}}
{{ color2 }}
</div>
<el-button @click="changeValue">更改</el-button>
</template>
<script setup>
import { ref ,computed } from 'vue'
const data = ref('红色')
// 写法一
const color = computed(() => {
return data.value
})
// 写法二
const color2 = computed({
get: () => data.value,
set: (value) => {
data.value = value
}
})
const changeValue = () => {
data.value = '白色'
}
</script>
watch
Vue2
export default {
data(){
return {
num:1,
info:{
name:'小米',
age:10
}
}
},
watch:{
// 基本数据类型
num(newData , oldData){
console.log(`num 变化前:${oldData},变化后:${newData}`)
},
// 引用数据类型
info:{
deep:true,
immediate:true,
handler(newData , oldData){
console.log(`num 变化前:${oldData},变化后:${newData}`)
}
},
// 监听某个属性值变化
'info.name':{
deep:true,
handler(newData , oldData){
console.log(`num 变化前:${oldData},变化后:${newData}`)
}
}
},
}
Vue3
- ref定义引用数据类型监听,参数一为要监听的数据,必须开启deep深度监听
- reactive定义引用数据类型监听,参数一为要监听的数据,默认开始深度监听
- 引用数据类型监听某个属性值,参数一需要使用函数返回其中的属性值,若ref定义则必须开启deep深度监听,reactive不用开启深度监听
- 一次监听多个值时,参数一为要监听的数据数组,函数传入的新旧值也为数组。引用数据类型拿不到旧值,如果需要则单独监听
- watchEffect实现监听优化(初始化运行和函数内使用的数据发生变化则会再次执行)
<template>
<div>
</div>
<el-button @click="changeValue">更改</el-button>
</template>
<script setup>
import { ref ,watch ,reactive , watchEffect} from 'vue'
const data = ref('红色')
const info = ref({
name:'json'
})
const info2 = reactive({
name:'js'
})
// ref定义基本数据类型监听
watch(data,(newData,oldData) => {
console.log('data发生了变化:', data.value)
console.log(newData,oldData)
})
// ref定义引用数据类型监听,必须开启deep深度监听
watch(info,(newData,oldData) => {
console.log('info发生了变化:', newData)
console.log(newData,oldData)
},{deep:true})
// ref定义引用数据类型监听某个属性值,参数一:使用函数返回某个属性值且必须开启deep深度监听
watch(() => info.value.name,(newData,oldData) => {
console.log('info.name发生了变化:', newData)
console.log(newData,oldData)
},{deep:true})
// reactive定义引用数据类型监听,默认开始深度监听
watch(info2,(newData,oldData) => {
console.log('info2 发生了变化:', newData)
console.log(newData,oldData)
})
// reactive定义引用数据类型监听某个属性值,参数一:使用函数返回某个属性值,默认开始深度监听
watch(() => info2.name ,(newData,oldData) => {
console.log('info2.name 发生了变化:', newData)
console.log(newData,oldData)
})
// 一次监听多个数据,一次监听多个值时,引用数据类型拿不到旧值,如果需要则单独监听即可
watch([data,info],(newDatas,oldDatas) => {
console.log('data,info 发生了变化:', newDatas,oldDatas) // ['黑色',{name:'json2}] ['红色',{name:'json2}]
})
// 一上来就会执行一次,当函数中使用的值发生改变会重新执行该函数
watchEffect(() => {
console.log('上来就执行')
console.log(info.value.name,'info更改了')
})
// 更改数据
const changeValue = () => {
data.value = '黑色'
info.value.name += 'json2'
info2.name = 'js2'
}
</script>
组件使用
- vue2需要引入并注册才能使用,vue3则引入即可使用
Vue2
<template>
<div id="app">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<TestVue></TestVue>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import TestVue from './components/TestVue.vue'
export default {
name: 'App',
components: {
HelloWorld,
TestVue
}
}
Vue3
<template>
<HelloWorld msg="Vite + Vue" />
<TestVue></TestVue>
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import TestVue from './components/TestVue.vue'
</script>
生命周期
- vue2 可以直接使用生命周期函数,vue3需要引入生命周期函数才能使用
Vue2 | Vue3 |
---|---|
beforeCreate | setup |
created | setup |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
activeted | onActiveted |
deactiveted | onDeactiveted |
beforeDestory | onBeforeUnmount |
destoryed | onUnmounted |
插槽
- vue2中的具名插槽slot可以使用非template标签,vue3则必须使用template标签才生效。推荐都在template标签上使用
Vue2
匿名插槽
// 定义
<template>
<div>
<slot>默认表示</slot>
</div>
</template>
// 使用
<TestVue><div>测试匿名插槽</div></TestVue>
具名插槽
// 定义
<template>
<div>
<slot name="title">默认表示</slot>
</div>
</template>
// 使用
// 写法一
<TestVue><div slot="title">测试匿名插槽</div></TestVue>
// 写法二
<TestVue><div #title>测试匿名插槽</div></TestVue>
作用域插槽
// 定义
<template>
<div>
<slot name="info" v-for="item in list" :row="item"></slot>
</div>
</template>
// 使用
// 写法一
<TestVue>
<template slot="info" slot-scope="{ row }">
<div>
<span>{{ row }}</span>
</div>
</template>
</TestVue>
// 写法二
<TestVue>
<template #info="{ row }">
<div>
<span>{{ row }}</span>
</div>
</template>
</TestVue>
Vue3
匿名插槽
// 定义
<template>
<div>
<slot>默认表示</slot>
</div>
</template>
// 使用
<TestVue><div>测试匿名插槽</div></TestVue>
具名插槽
// 定义
<template>
<div>
<slot name="title">默认表示</slot>
</div>
</template>
// 使用
// 写法一:
<TestVue><template v-slot:title>测试匿名插槽</template></TestVue>
// 写法二:
<TestVue><template #title>测试匿名插槽</template></TestVue>
作用域插槽
// 定义
<template>
<slot name="title" v-for="item in list" :row="item" >测试</slot>
</template>
<script setup>
import {ref} from 'vue'
const list = ref([
{name: 'Apple', price: 10},
{name: 'Banana', price: 5},
{name: 'Orange', price: 8},
{name: 'Grape', price: 15}
])
</script>
// 使用
// 写法一
<TestVue><template v-slot:title="{row}">{{ row }}</template></TestVue>
// 写法二
<TestVue><template #title="{row}">{{ row }}</template></TestVue>
获取当前组件实例
Vue2
export default {
mounted(){
console.log(this)
}
}
</script>
Vue3
<script setup>
import {getCurrentInstance} from 'vue'
const compInstance = getCurrentInstance()
console.log(compInstance)
</script>
nextTick
Vue2
export default {
mounted(){
this.$nextTick(() => {
})
}
}
</script>
Vue3
<script setup>
import {nextTick} from 'vue'
// 写法一
nextTick(() => {
处理逻辑
})
// 写法二
await nextTick()
.... 处理逻辑...
</script>
mixins和hooks
- mixins和hooks都是为了某些公共数据和方法的公用
- mixins在重名情况下会发生覆盖【项目中定义的全局混入showError方法】,而每个hook都是一个独立的函数,可以单独使用或组合使用不会造成同名覆盖的情况。
- Hooks使得逻辑更加模块化,易于理解和维护。
Vue2
// 定义公共mixins
export default {
data(){
return {
common:'test'
}
},
methods:{
changeCommon(){
this.common = 'changed'
}
}
}
// 使用mixins
<template>
<div>
{{ common }}
</div>
</template>
<script>
import mixins from '../mixins.js'
export default {
mixins:[mixins],
data(){
return {
list:[
{
name:'小米'
},
{
name:'小米2'
}
]
}
},
mounted(){
}
}
Vue3
- hook的命名需要以
use
开头,例如useTimeout
,这样开发者看到useXXX
即可明白这是一个Hook。Hook的名称需要清楚地表明其功能。 - hooks尽量要功能单一化提高复用性
- vueUse 中文文档
// 定义获取鼠标位置hooks
import { ref, onMounted, onUnmounted } from 'vue'
const useMouse = () => {
const x = ref(0)
const y = ref(0)
// 获取鼠标位置
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
export default useMouse
// 使用
<template>
<div>
{{ x }} , {{y}}
</div>
</template>
<script setup>
import useMouse from './useMouse.js'
const {x,y} = useMouse()
</script>
组件通信对比
props
- 子组件定义props,父组件向子组件传递数据,实现父组件向子组件通信
- 父组件向子组件传递函数,子组件内调用该函数,实现子组件向父组件通信
Vue2
// 写法一
export default {
props:['text'],
}
// 写法二
export default {
props:{
text:{
type: String,
required: true,
default: 'Hello, Vue.js!'
}
},
}
// 写法三
export default {
props:{
text:{
type: String,
required: true,
default: 'Hello, Vue.js!',
validator: (value) => {
return /^[A-Za-z\s]+$/.test(value);
}
}
},
}
Vue3
// 写法一
<script setup>
import {defineProps} from 'vue';
const props = defineProps(['type','text'])
</script>
// 写法二
<script setup>
import {defineProps} from 'vue';
const props = defineProps({
type: {
type: String,
required: true,
validator: (value) => ['primary','secondary', 'tertiary'].includes(value)
},
size: {
type: String,
default: 'normal'
}
})
</script>
// 写法三 ts中利用withDefaults方法进行对defineProps默认值的填写
<script lang="ts" setup >
import {defineProps , withDefaults} from 'vue';
const props = withDefaults(defineProps<{text:string}>(),{
text:'测试withDefaults'
})
</script>
emit
- 子组件通过定义自定义的emit事件,父组件通过@事件名进行子组件向父组件传递数据
- 通过特殊事件名称和props的配合可以实现父子组件数据同步
Vue2
export default {
methods:{
changeValue(){
this.$emit('change','测试')
}
}
}
Vue3
// 定义多个
<script setup >
import {defineEmits} from 'vue';
const Emits = defineEmits(['change'])
const changeValue = () => {
Emits('change','测试')
}
</script>
// 定义多个和添加校验
<script setup >
import {defineEmits} from 'vue';
// 当校验函数不通过的时候,控制台会输出一个警告,但是emit事件会继续执行
const Emits = defineEmits({
change: (value) => typeof value == 'string'
})
</script>
ref
- 通过ref获取组件实例,进行组件内数据和方法的调用,实现父组件向子组件获取数据
Vue2
// 获取当前组件实例
export default {
mounted(){
console.log(this)
},
}
// 获取其他组件实例
<template>
<div>
<el-form ref="formData"></el-form>
</div>
</template>
<script>
export default {
mounted(){},
methods:{
validate(){
this.$refs.formData.validate()
}
}
}
Vue3
- 获取某个组件实例,定义的ref名称要和组件内定义的变量名一致
- 想通过ref获取的组件的属性或方法,被获取的组件必须通过
defineExpose
方法暴露属性或方法
// 获取当前组件实例
<script setup >
import {getCurrentInstance} from 'vue';
// setup 函数中是直接可以获取当前组件实例
const currentInstance = getCurrentInstance()
</script>
// 获取其他组件实例
<el-form ref="formRef"></el-form>
<script setup >
const formRef = ref(null);
const submitForm = () => {
formRef.value.validate((valid) => {
});
}
</script>
sync(父子组件同步)
- 通过 props + emit 实现 .sync 实现父子数据同步
- vue2中要使用update:xxx 语法,vue3中要使用mdoel
Vue2
- 子组件
<template>
<div>
<el-button @click="changeText">更改text</el-button>
{{ currentText }}
</div>
</template>
<script>
export default {
props:{
text:{
type:String,
default:''
}
},
data(){
return {
currentText: ''
}
},
watch:{
text:{
immediate:true,
handler(newText){
this.currentText = newText
}
}
},
methods:{
changeText(){
this.$emit('update:text','更新text')
}
}
}
</script>
<style scoped>
</style>
- 父组件
<template>
<div id="app">
<TestVue :text.sync="text"></TestVue>
</div>
</template>
<script>
import TestVue from './components/TestVue.vue'
export default {
name: 'App',
components: {
TestVue
},
data(){
return {
text: 'Hello Vue.js!'
}
}
}
</script>
Vue3
vue3中取消的.sync 语法 更改成功了 v-model:xxxx
语法
- 子组件
<template>
<div>
<el-button @click="changeText">更改text</el-button>
{{ currentText }}
</div>
</template>
<script setup>
import { ref ,defineProps , watchEffect , defineEmits} from 'vue';
const props = defineProps(['text'])
// 定义emit方法
const emits = defineEmits(['update:text'])
const currentText = ref('')
watchEffect(() => {
currentText.value = props.text;
})
const changeText = () => {
emits('update:text','更新text')
};
</script>
- 父组件
<template>
<TestVue v-model:text="text"></TestVue>
</template>
<script setup>
import {ref} from 'vue'
import TestVue from './components/TestVue.vue'
const text = ref('测试')
</script>
v-model(父子组件同步)
- 利用 Vue2 v-model 是
:value
和@input
事件的语法糖实现封装v-model 实现父子数据同步 - 利用 Vue3 v-model 是
:modelValue
和update:modelValue
的语法糖实现v-model 实现父子数据同步
Vue2
- 子组件
<template>
<div>
<input v-model="text" />
</div>
</template>
<script>
export default {
name: "HelloWorld",
components: {},
props: {
value: {
type: String,
default: "",
},
},
data() {
return {
text: "",
};
},
watch: {
value: {
immediate: true,
handler(n) {
this.text = n;
},
},
text(n) {
this.$emit("input", n);
},
},
};
</script>
<style lang="less" scoped>
</style>
- 父组件
<template>
<div id="app">
<HelloWorld v-model="text" />
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
name: "App",
components: {
HelloWorld,
},
data() {
return {
text: "测试",
};
},
};
</script>
Vue3
- 子组件
<template>
<div>
<el-input v-model="currentText"></el-input>
</div>
</template>
<script setup>
import { ref, defineProps, defineEmits, watch } from 'vue';
const props = defineProps(['modelValue'])
// 定义emit方法
const emits = defineEmits(['update:modelValue'])
const currentText = ref('')
watch(props.modelValue, () => {
currentText.value = props.modelValue;
}, { immediate: true })
watch(currentText, (n) => {
emits('update:modelValue', n)
})
</script>
- 父组件
<template>
<TestVue v-model="text"></TestVue>
</template>
<script setup>
import {ref} from 'vue'
import TestVue from './components/TestVue.vue'
const text = ref('测试')
</script>
作用域插槽
利用作用域插槽实现子组件向父组件传递数据,父组件向子组件传递html结构
Vue2
// 定义
<template>
<div>
<slot name="info" v-for="item in list" :row="item"></slot>
</div>
</template>
// 使用
// 写法一
<TestVue>
<template slot="info" slot-scope="{ row }">
<div>
<span>{{ row }}</span>
</div>
</template>
</TestVue>
// 写法二
<TestVue>
<template #info="{ row }">
<div>
<span>{{ row }}</span>
</div>
</template>
</TestVue>
Vue3
// 定义
<template>
<slot name="title" v-for="item in list" :row="item" >测试</slot>
</template>
<script setup>
import {ref} from 'vue'
const list = ref([
{name: 'Apple', price: 10},
{name: 'Banana', price: 5},
{name: 'Orange', price: 8},
{name: 'Grape', price: 15}
])
</script>
// 使用
// 写法一
<TestVue><template v-slot:title="{row}">{{ row }}</template></TestVue>
// 写法二
<TestVue><template #title="{row}">{{ row }}</template></TestVue>
EventBus (兄弟组件之间)
Vue2
- 定义Event.js
import Vue from 'vue'
const Bus = new Vue();
export default Bus;
- A组件中 【接收消息】
<template>
<div class="hello">
</div>
</template>
<script>
import Bus from './Event.js'
export default {
name: 'HelloWorld',
mounted(){
// 订阅消息
Bus.$on('message', this.getMessage)
},
methods:{
getMessage(message){
console.log(message)
}
}
}
</script>
- B组件中 【发送消息】
<template>
<div>
<el-button @click="changeMessage"> 发送消息 </el-button>
</div>
</template>
<script>
import Bus from './Event.js'
export default {
methods:{
// 发送消息
changeMessage(){
Bus.$emit('message','发送消息')
}
}
}
Vue3
使用 mitt.js 插件
- 安装 mitt
npm install mitt -S
- 定义公共Bus
import mitt from "mitt";
const emitter = mitt();
export default emitter
- A组件使用
<template>
<div>
</div>
<el-button @click="changeMessage">发送消息</el-button>
</template>
<script setup>
import Bus from './evevtBus.js'
// 发送消息
const changeMessage = () => {
Bus.emit('message','消息')
}
</script>
- B组件使用
<template>
<div>
</div>
</template>
<script setup>
import Bus from './evevtBus.js'
// 接受消息
Bus.on('message',(value) => {
console.log(value)
})
</script>
provide和inject
- props 的升级版,具有属性穿透的特性,通常用于父组件往包裹中的任意组件中注入数据
- provide和inject提供的数据没有响应式
Vue2
- 父组件
<script>
export default {
name: "App",
provide(){
return {
reload:this.changeRow
}
},
methods: {
changeRow(row){
console.log(row)
}
},
};
</script>
- 子组件
<script>
export default {
inject:['reload'],
mounted() {
this.reload({title:'xxx'})
},
};
</script>
Vue3
- 父组件
// 父组件
<script setup>
import { provide, ref } from 'vue';
const name = ref('lei')
provide('name',name) // 提供数据
provide('changeName', (value) => {
name.value = value
})
}
</script>
- 子组件
<script setup>
import { inject } from 'vue';
const name = inject('name')
const changeName = inject('changeName')
const handkeClick = () => { // 注册点击事件
changeName('yang') // 当事件触发,调用父组件的changeName 方法进行数据改变
}
</script>
attrs和listeners
- 通常用于封装第三方组件,透传一些属性和方法
- 子组件中的
$attrs
可以拿到props已定义
、class
和style
除外的父组件传来的所有属性
,必须结合v-bind
使用,:
简写形式只能绑定一个属性,v-bind
则可以绑定批量属性。 - 子组件中的
$listeners
可以拿到除.native
修饰的事件外的所有事件
。必须结合v-on
使用,v-on
可以绑定批量,@
则只能绑定一个。 - vue2将
$attrs
和$listeners
分别绑定在组件的$atrrs
和$listeners
两个对象上 - vue3则将
$attrs
和$listeners
都放在了$attrs
参数上
Vue2
- 封装集成el-button的组件
<template>
<div>
<el-button v-bind="$attrs" v-on="$listeners">{{ text }}</el-button>
</div>
</template>
<script>
export default {
props:['text']
}
</script>
- 使用
<TestVue text="测试" type="primary" plain></TestVue>
Vue3
- 子组件
<template>
<div>
<el-select v-on="$attrs" v-bind="$attrs" style="width: 240px">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"
:disabled="item.disabled" />
</el-select>
</div>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps(['options'])
</script>
- 父组件
<TestVue v-model="text" :options="[{label:'测试',value:1}]" @visible-change="visibleChange"></TestVue>
Vue3使用时的特殊情况
对定义的响应式对象直接进行解构,其属性会失去响应式
直接利用对象解构获取的数据会失去响应式(解构赋值会创建一个新的对象,它会绕过Proxy对象的代理功能,因此无法实现响应式),建议使用.value形式去修改数据。
解决对象解构响应式丢失问题
利用toRefs方法包裹响应式数据实现解构
// 案例一
<template>
<div>
解构数据:
{{ name }}
{{ age }}
</div>
<el-button @click="changeValue">更改数据</el-button>
</template>
<script setup>
import { reactive, toRefs } from 'vue'
const data = reactive({
name: 'John',
age: 20,
})
const changeValue = () => {
data.name = 'Jane';
data.age = 25;
}
// 利用 toRefs 进行解构使用
const { name, age } = toRefs(data)
</script>
// 案例二
<template>
<div>
解构数据:
{{ name }}
{{ age }}
</div>
<el-button @click="changeValue">更改数据</el-button>
</template>
<script setup>
import { reactive, toRefs } from 'vue'
const data = reactive({
name: 'John',
age: 20,
})
// 利用 toRefs 进行解构使用
let { name, age } = toRefs(data)
// 并修改解构后的值
const changeValue = () => {
name.value = 'Jane';
age.value = 25;
}
</script>
利用第三方插件实现解构响应式和porps的解构响应式
npm i -D @vue-macros``/reactivity-transform
// vite.config.js
import ReactivityTransform from '@vue-macros/reactivity-transform/vite'
export default defineConfig({
plugins: [ReactivityTransform()],
})
deep样式的书写
vue3 将
::v-deep
废弃,使用:deep()
.a :deep(.b) { /* ... */ }
defineExpose() 对外暴露属性和方法
- 组件A 对外暴露属性和方法
<script setup>
import { defineExpose} from 'vue'
const a = ref('')
const test = () => {
}
// 对外暴露方法和属性
defineExpose({
a,
test
})
</script>
- 通过ref获取组件的属性和方法
<Test ref="testRef"></Test>
<script setup>
import { ref , nextTick,watch} from 'vue'
const testRef = ref(null)
watch(visible,() => {
nextTick(() => {
console.log(menusRef.value)
})
})
</script>
Vue3好用的新特性
传送门
<template>
// 传送到#some-id节点上
<teleport to="#some-id" ref="target">
<div>Portal Content</div>
</teleport>
</template>
jsx语法
- 安装@vue/babel-plugin-jsx插件
npm i @vitejs/plugin-vue-jsx -D
- 文件的lang要用 tsx/jsx 否则不能使用会报错
// 定义
<script lang="jsx">
export default {
render() {
return (
<div>JSX</div>
)
},
}
</script>
// 使用
<template>
<TestVue></TestVue>
</template>
<script setup lang="jsx">
import TestVue from './components/TestVue.vue'
</script>