参考资料:
本文全部基于 vue3。
1. vue-cli 创建vue3项目
2. vue3升级调整 升级依赖后,需要对代码进行相应的调整。以下是几个关键的迁移步骤。
全局 API 迁移 Vue 3 对全局 API 进行了重构,许多全局方法现在需要通过 createApp 实例来调用。
main.js & router/index.js & store/index.js
Vue 2 示例
1 2 3 4 5 6 7 8 9 10 import Vue from 'vue' ;import App from './App.vue' ;import router from './router' ;import store from './store' ;Vue .config .productionTip = false ;new Vue ({ router, store, render : h => h (App ), }).$mount('#app' );
Vue 3 示例
1 2 3 4 5 6 7 8 import { createApp } from 'vue' ;import App from './App.vue' ;import router from './router' ;import store from './store' ;const app = createApp (App ); app.use (router); app.use (store); app.mount ('#app' );
实例属性迁移 Vue 2 中的实例属性,如 $mount 和 $destroy,在 Vue 3 中已经有所变化。
Vue 2 示例
1 2 3 4 const vm = new Vue ({ }); vm.$mount('#app' );
Vue 3 示例
1 2 3 4 const app = createApp ({ }); app.mount ('#app' );
事件 API 迁移 Vue 3 删除了 $on, $off, 和 $once 方法,建议使用 mitt[2] 这样的事件库作为替代。
Vue 2 示例
1 2 3 4 const vm = new Vue (); vm.$on('event' , () => { });
Vue 3 示例
1 2 3 4 5 import mitt from 'mitt' ;const emitter = mitt (); emitter.on ('event' , () => { });
指令迁移 Vue 3 对指令的定义方式进行了调整。
Vue 2 示例
1 2 3 4 5 Vue .directive ('focus' , { inserted : function (el ) { el.focus (); } });
Vue 3 示例
1 2 3 4 5 6 7 const app = createApp (App ); app.directive ('focus' , { mounted (el ) { el.focus (); } }); app.mount ('#app' );
组件生命周期钩子迁移 Vue 3 对一些生命周期钩子进行了重命名,例如 beforeDestroy 改为 beforeUnmount,destroyed 改为 unmounted。
Vue 2 示例
1 2 3 4 5 6 7 8 export default { beforeDestroy ( ) { console .log ('Component is about to be destroyed' ); }, destroyed ( ) { console .log ('Component has been destroyed' ); } };
Vue 3 示例
1 2 3 4 5 6 7 8 export default { beforeUnmount ( ) { console .log ('Component is about to be unmounted' ); }, unmounted ( ) { console .log ('Component has been unmounted' ); } };
3. 使用 Vue 3 的新特性 3.0 createApp 多个应用实例应用实例并不只限于一个。
createApp API 允许你在同一个页面中创建多个共存的 Vue 应用,而且每个应用都拥有自己的用于配置和全局资源的作用域。
1 2 3 4 5 6 7 import { createApp } from 'vue' const app1 = createApp ({...}) app1.mount ('#container-1' )const app2 = createApp ({...}) app2.mount ('#container-2' )
3.1 Composition API Composition API(组合式API / 函数式API )是 Vue 3 中的一个重要新特性,它提供了一种更灵活、更可组合的方式来组织组件逻辑。
3.1.1 reactive 作用 :创建响应式对象,非包装对象,可以认为是模版中的状态。
setup(){...} 组合式 API 的入口,返回的对象/方法会暴露给模板和组件实例使用。同时也没有了 this. 访问
<template>可以放兄弟节点
reactive 类似 useState,如果参数是字符串、数字会报警告”value cannot be made reactive “,所以应该设置对象,才可以数据驱动页面
结合单文件组件使用的组合式 API,推荐通过 <script setup> 以获得更加简洁及符合人体工程学的语法。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <template> <div> hello-vue3 {{ obj1.myname }}-{{ obj1.myage }} <button @click="handleClick">change</button> <div>todo list-{{obj2.mytext}}</div> <input type="text" v-model="obj2.mytext" /> <button @click="handleAdd">add</button> <ul> <li v-for="data in obj2.datalist" :key="data">{{ data }}</li> </ul> </div> </template> <script> import { reactive } from "vue" export default { // vue3老写法或者vue写法中 beforeCreate/created 生命周期 ===> setup setup() { console.log("setup") // 定义状态obj1,obj2,(同一个组件reactive支持多个定义) const obj1 = reactive({ myname: "jerry", myage: 18, }) const obj2 = reactive({ mytext: "", datalist: [], }) const handleClick = () => { obj1.myname = "tom" } const handleAdd = () => { obj2.datalist.push(obj2.mytext) obj2.mytext = '' } return { obj1, obj2, handleClick, handleAdd } }, } </script>
3.1.2 ref 作用 :创建一个包装式对象,含有一个响应式属性 value,可以 . 访问出来(即 .value 就是dom节点对象)。
ref 与 reactive 的差别就是 ref 没有包装属性 value(也会被拦截,也可以当做状态字段用在dom节点上)
const count = ref(0),可以接受普通数据类型, count.value++
.value 如果是 <input type="text" ref="mytext">文本标签,取值即为 mytext.value.value 为输入内容
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <template> <div> hello-vue3 {{ myname }} <button @click="handleClick">change</button> </div> </template> <script> import { ref } from "vue" export default { setup() { const myname = ref("jerry") //.value属性拦截, dom使用 {{myname}} const handleClick = () => { myname.value = "tom" //修改只能对 .value 属性修改 } return { myname, handleClick, } }, } </script>
3.1.3 toRefs 作用 :可以在dom节点省略对象名直接访问对象的属性
...toRefs(obj) 实际用法要对象展开式。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <template> <div> hello-vue3 {{ myname }}-{{ myage }} <button @click="handleClick">change</button> </div> </template> <script> import { reactive, toRefs } from "vue" export default { setup() { const obj1 = reactive({ myname: "jerry", myage: 18, }) const handleClick = () => { obj1.myname = "tom" } return { ...toRefs(obj1), //toRefs可以让obj1省略,在dom节点上直接访问属性值 handleClick, } }, } </script>
3.1.4 props 作用:状态字段通信。参考官网使用
示例:
navbar.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <template> <div> --{{myname}}-- </div> </template> <script> export default { props: ["myname"], setup(props) { console.log(props.myname) } } </script>
验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template> <div> 通信 <navbar myname="home"></navbar> </div> </template> <script> import navbar from './components/Navbar.vue' export default { components: { navbar } } </script>
3.1.5 emit 作用:触发父组件中定义的时间,来实现不同组件之间的通信。
setup(props, { emit }) {emit} 作为第二个参数,结构接参
emit("event") 触发父组件中名字为event的事件 @event=”change”
父组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <template> <div> 通信 <navbar myname="home" @showEvent="change"></navbar> <sidebar v-show="obj.isShow"></sidebar> </div> </template> <script> import navbar from "./components/Navbar.vue" import sidebar from "./components/Sidebar.vue" import { reactive } from "vue" export default { components: { navbar, sidebar, }, setup() { const obj = reactive({ isShow: true, }) const change = () => { obj.isShow = !obj.isShow } return { obj, change, } }, } </script>
子组件1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template> <div> --{{ myname }}-- <button @click="handleShow">show</button> </div> </template> <script> export default { props: ["myname"], // {emit} 作为第二个参数,结构接参 setup(props, { emit }) { console.log(props.myname) const handleShow = () => { console.log("handleShow") emit("showEvent") } return { handleShow, } }, } </script>
子组件2:
1 2 3 4 5 6 7 8 9 <template> <div> <ul> <li>111</li> <li>222</li> <li>333</li> </ul> </div> </template>
3.2 Teleport Teleport 允许你将组件的 DOM 渲染到一个特定的 DOM 节点之外。
示例
1 2 3 4 5 6 7 8 9 <template> <div > <teleport to ="#modals" > <div class ="modal" > <p > This is a modal</p > </div > </teleport > </div > </template>
3.3 Fragments 在 Vue 3 中,组件可以返回多个根节点,从而摆脱了 Vue 2 中必须有单一根节点的限制。
示例
1 2 3 4 5 6 7 <template> <div > <header > Header Content</header > <main > Main Content</main > <footer > Footer Content</footer > </div > </template>
3.4 生命周期 Vue 2 & Vue 3 生命周期对比
vue2
vue3
beforeCreate
setup(()=>{})
created
setup(()=>{})
beforeMount
onBeforeMount(()=>{})
mounted
onMounted(()=>{})
beforeUpdate
onBeforeUpdate(()=>{})
updated
onUpdated(()=>{})
beforeDestroy
onBeforeUnmount(()=>{})
destroyed
onUnmounted(()=>{})
—分割线—
—分割线—
activated
onActivated(()=>{})
deactivated
onDeactivated(()=>{})
errorCaptured
onErrorCaptured(()=>{})
总结: Vue2和Vue3钩子变化不大,beforeCreate 、created 两个钩子被 setup() 钩子来替代。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <template> <div> 生命周期 <ul> <li v-for="data in obj.list" :key="data">{{data}}</li> </ul> </div> </template> <script> import axios from 'axios' import {reactive, onBeforeMount, onMounted} from 'vue' export default { setup() { const obj = reactive({ list: [] }) onBeforeMount(() => { console.log("onBeforeMounted") }) onMounted(() => { console.log("dom上树、axios、事件监听、setInterval......") setTimeout(() => { obj.list = ["111", "222", "333"] }, 2000) }) return { obj } } } </script>
3.5 计算属性 作用:与vue2一样,值没有变化时只会计算一次。注重结果,必须有返回值。
computed(() => { return xxx } 计算属性新的写法。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <template> <div> 搜索:<input type="text" v-model="obj.mytext" />{{obj.mytext}} <ul> <li v-for="data in computedList" :key="data"> {{ data }} </li> </ul> </div> </template> <script> import { reactive, computed } from "vue" export default { setup() { const obj = reactive({ mytext: '', datalist: ["aaa", "abb", "abc", "bac", "caa", "cba"], }) //计算属性写法 const computedList = computed(() => { return obj.datalist.filter(item => item.includes(obj.mytext)) }) return { obj, computedList } }, } </script>
3.6 watch 作用:监听状态字段值的改变,触发回调函数执行。注重过程。
watch(监听函数, 回调函数) 监听函数监听状态字段,回调函数在监听字段发生改变时触发执行。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <template> <div> 搜索:<input type="text" v-model="obj.mytext" @input="handleInput" /> {{ obj.mytext }} <ul> <li v-for="data in obj.datalist" :key="data"> {{ data }} </li> </ul> </div> </template> <script> import { reactive, watch } from "vue" export default { setup() { const obj = reactive({ mytext: "", datalist: ["aaa", "abb", "abc", "bac", "caa", "cba"], oldlist: ["aaa", "abb", "abc", "bac", "caa", "cba"], //保留原列表不变 }) //监听状态字段值的改变,都会触发回调函数调用(第二个参数) watch( () => obj.mytext, () => { obj.datalist = obj.oldlist.filter(item => item.includes(obj.mytext)) } ) return { obj, } }, } </script>
3.7 自定义hooks 作用:自定义hooks可以实现函数编程的复用,更加简洁高效。可以理解为函数封装
示例:
1 2 3 4 5 6 7 8 9 10 11 import { ref } from 'vue' function useCount ( ) { const count = ref (1 ) const addCount = (num = 1 ) => count.value += num return { count, addCount } }export { useCount }
hooks 封装使用示例:
3.8 路由 1 2 3 4 5 6 7 8 import { useRoute, useRouter } from 'vue' export default { setup ( ) { const route = useRoute () const router = useRouter () } }
3.9 vuex 公共状态管理 store 1 2 3 4 5 6 7 import { useStore } from 'vuex' export default { setup ( ) { const store = useStore () } }
超级好用的替代方案:provide, inject 是 vue-composition-api 的一个新功能,依赖注入功能。(provide-提供, inject-注入)
1 2 3 4 5 6 7 8 9 10 11 import { provide, inject} from 'vue' const showStatus = ref (true )provide ("showStatus" , showStatus)onMounted (() => { const showStatus = inject ("showStatus" ) showStatus.value = false })
4. 测试和调试 在完成代码迁移后,确保对整个项目进行全面的测试和调试。以下是一些推荐的测试和调试步骤。
单元测试(使用 Jest) 使用 Jest 或 Mocha 等测试框架,编写和运行单元测试,确保所有功能正常工作。
示例
1 2 3 4 5 6 7 8 9 10 11 import { mount } from '@vue/test-utils' ;import HelloWorld from '@/components/HelloWorld.vue' ;describe ('HelloWorld.vue' , () => { it ('renders props.msg when passed' , () => { const msg = 'new message' ; const wrapper = mount (HelloWorld , { props : { msg } }); expect (wrapper.text ()).toMatch (msg); }); });
端到端测试(使用 Cypress) 使用 Cypress 或 Nightwatch 等工具进行端到端测试,模拟用户操作,确保应用在真实使用场景中表现正常。
示例
1 2 3 4 5 6 describe ('My First Test' , () => { it ('Visits the app root url' , () => { cy.visit ('/' ); cy.contains ('h1' , 'Welcome to Your Vue.js App' ); }); });
调试 使用 Vue Devtools 来调试 Vue 3 应用。确保你安装了最新版本的 Vue Devtools,并在开发者工具中启用了 Vue 3 支持。
5. vue3组件定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <div id ="box" > {{myname}} <navbar myname ="aaa" > <div > 111111111111111</div > </navbar > <sidebar > </sidebar > </div > <script > var obj = { data ( ) { return { myname : "jerry" } }, methods : {} computed : {} } var app = Vue .createApp (obj) app.component ("navbar" , { props : ["myname" ], template : ` <div> navbar-{{myname}} <slot></slot> </div> ` }) app.component ("sidebar" , { template : ` <div>123123123</div> ` }) app.mount ("#box" ) </script >
6. vue3自定义指令 6.1 轮播
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 <div id ="box" > <header > 导航</header > <div class ="swiper" > <div class ="swiper-wrapper" > <div class ="swiper-slide" v-for ="(item, index) in datalist" :key ="item" v-swiper ="{index: index, length: datalist.length}" > {{item}} </div > </div > <div class ="swiper-pagination" > </div > <div class ="swiper-button-prev" > </div > <div class ="swiper-button-next" > </div > </div > <footer > 底部内容</footer > </div > <script > var obj = { data ( ) { return { datalist : [] } }, mounted ( ) { setTimeout (() => { this .datalist = ["aaa" , "bbb" , "ccc" ] }, 2000 ) }, } var app = Vue .createApp (obj) app.directive ("swiper" , { mounted (el, binding ) { console .log ("inserted" , el, binding.value ) let { index, length } = binding.value if (binding.value = length) { console .log ("new Swiper" ) new Swiper (".swiper" , { loop : true , pagination : { el : '.swiper-pagination' , }, navigation : { nextEl : '.swiper-button-next' , prevEl : '.swiper-button-prev' , }, autoplay : { delay : 2500 , disableOnInteraction : false , }, }) } } }) app.mount ("#box" ) </script >
6.2 路由 router/index.js - vue2可以用 * 做默认匹配,vue3中需要使用 \
1 2 3 4 5 6 7 8 const routes = [ ... { path : '/' , redirect : '/films' } ]