Vue2和Vue3的写法差异

419 阅读12分钟

为什么要使用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>

image.png

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的读取操作

image.png

<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】,非引用数据类型修改值检测不到改值的响应变化

image.png

  • 当定义引用数据类型响应式数据时,推荐使用 reactive(),reactive 创建出来的是一个干净的Proxy代理对象可以省去取值.value的操作

image.png

<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需要引入生命周期函数才能使用
Vue2Vue3
beforeCreatesetup
createdsetup
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
activetedonActiveted
deactivetedonDeactiveted
beforeDestoryonBeforeUnmount
destoryedonUnmounted

插槽

  • 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>

image.png

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>

image.png

v-model(父子组件同步)

  • 利用 Vue2 v-model 是 :value@input 事件的语法糖实现封装v-model 实现父子数据同步
  • 利用 Vue3 v-model 是 :modelValueupdate: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形式去修改数据。

image.png

解决对象解构响应式丢失问题

利用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的解构响应式

vue-macros

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>