一、 项目地址
学习管理系统地址: https://bushanjiangzi.gitee.io/vue3manage/#/
源码地址:https://gitee.com/bushanjiangzi/vue3-element-plus
二、新特性
1. 实例化
1 1 import { createApp } from 'vue' 2 2 import App from './App.vue' 3 3 import router from './router' 4 4 import store from './store' 5 5 import ElementPlus from 'element-plus' 6 6 import 'element-plus/lib/theme-chalk/index.css' 7 7 import './assets/css/public.css' 8 8 import './assets/css/reset.css' 9 9 10 10 createApp(App) 11 11 .use(store) 12 12 .use(router) 13 13 .use(ElementPlus) 14 14 .mount('#app')
2. reactive 和 ref
reactive
类似于vue2.x 中的 Vue.observable(),vue3.x的reactive
和ref
取代了vue2.x中的data
数据定义
reactive
和ref
取代了vue2.x中的data
数据定义1 <template> 2 <div class="home"> 3 <HelloWorld :propsMsg="propsMsg"> 4 <template #backBtn> 5 <router-link to="/setup"> 6 <el-button type="primary" plain>Setup</el-button> 7 </router-link> 8 </template> 9 </HelloWorld> 10 <h2>HelloWorld Input</h2> 11 <input v-model="myInput" /> 12 <h2>Conputed String</h2> 13 <input v-model="firstInput" /> 14 <input v-model="secondInput" /> 15 <div>{{ state.total }}</div> 16 <h2>Conputed Object</h2> 17 <input v-model="state.first" /> 18 <input v-model="state.second" /> 19 <div>total:{{ state.total2 }}</div> 20 <div>doubleCount:{{ doubleCount }}</div> 21 </div> 22 </template> 23 24 <script> 25 import HelloWorld from '@/components/HelloWorld.vue' 26 import { reactive, ref, watchEffect, watch, provide, computed } from 'vue' 27 import setupTest from '@/composition/setup' 28 29 export default { 30 name: 'Home', 31 components: { 32 HelloWorld 33 }, 34 setup() { 35 const { propsMsg, myInput } = setupTest() 36 propsMsg.name = ' World' 37 const firstInput = ref() 38 const secondInput = ref() 39 const state = reactive({ 40 first: 0, 41 second: 0, 42 total: 0, 43 total2: 0 44 }) 45 watchEffect(() => { 46 state.total = firstInput.value + secondInput.value 47 state.total2 = parseInt(state.first) + parseInt(state.second) 48 }) 49 watch([firstInput, secondInput], (newValues, prevValues) => { 50 console.log(firstInput.value, secondInput.value, newValues, prevValues) 51 }) 52 const doubleCount = computed({ 53 get() { 54 return state.total2 * 2 55 }, 56 set(newVal) { 57 state.total2 = newVal / 2 58 } 59 }) 60 provide('provideData', 'provide data from home') 61 return { 62 propsMsg, 63 myInput, 64 firstInput, 65 secondInput, 66 state, 67 doubleCount 68 } 69 } 70 } 71 </script>
3. watch & watchEffect
3.x中watch
支持监听单个属性,也支持监听多个属性,相比2.x的watch
更灵活,多个时第一个参数是要监听的数组,第二个参数是回调函数,返回参数是新值;3.x中watchEffect
方法会返回一个方法(俗称副作用,只要返回函数里的依赖项发生变化就回执行回调函数),watch
跟watchEffect
不同的地方在于,watchEffect
注册后会立即调用,而watch
默认不会,除非显示指定immediate=true
,并且watchEffect
可以停止监听
4. 计算属性 computed
2.x和3.x中的computed
都支持getter和setter,写法一样,只是3.x中是组合函数式
5. provide和inject
父组件:provide('provideData', 'provide data from home')
子孙组件:inject('provideData')
1 <template> 2 <div class="hello"> 3 <h1 class="left">{{ propsMsg.msg }} {{ propsMsg.name }} {{ homeInput }}</h1> 4 <div class="right"> 5 <el-button type="primary" plain @click="toElement">Element-Plus</el-button> 6 <slot name="backBtn"></slot> 7 </div> 8 </div> 9 </template> 10 11 <script> 12 // eslint-disable-next-line 13 import { computed, inject, onMounted, getCurrentInstance } from 'vue' 14 import store from '@/store/index' 15 import router from '@/router/index' 16 import { ElMessage } from 'element-plus' 17 export default { 18 name: 'HelloWorld', 19 props: { 20 propsMsg: { 21 type: Object, 22 require: true 23 } 24 }, 25 setup() { 26 const homeInput = computed(() => { 27 return store.state.homeInput 28 }) 29 const toElement = () => { 30 router.push({ name: 'ElementIndex' }) 31 } 32 ElMessage.success(inject('provideData')) 33 onMounted(() => { 34 // const { ctx } = getCurrentInstance() 35 // ctx.$message.success(inject('provideData')) 36 // console.log(inject('provideData')) 37 }) 38 return { 39 homeInput, 40 toElement 41 } 42 } 43 } 44 </script>
6. getCurrentInstance获取当前组件实例
import { getCurrentInstance } from 'vue'
const { ctx } = getCurrentInstance()
7. 插槽slot
父组件:```
```
子组件:```
```
8. 生命周期
3.x移除了2.x中的beforeCreate
和created
钩子,通过setup
方法代替
1 import { 2 onBeforeMount, 3 onMounted, 4 onBeforeUpdate, 5 onUpdated, 6 onBeforeUnmount, 7 onUnmounted 8 } from 'vue' 9 10 export default { 11 setup() { 12 onBeforeMount(() => { 13 console.log('onBeforeMount') 14 }) 15 onMounted(() => { 16 console.log('onMounted') 17 }) 18 onBeforeUpdate(() => { 19 console.log('onBeforeUpdate') 20 }) 21 onUpdated(() => { 22 console.log('onUpdated') 23 }) 24 onBeforeUnmount(() => { 25 console.log('onBeforeUnmount') 26 }) 27 onUnmounted(() => { 28 console.log('onUnmounted') 29 }) 30 } 31 }
三、Composition API及代码复用
vue2.x中,所有的数据都在data
方法中定义返回,方法定义在methods
下面,并通过this
调用vue3.x中,所有的代码逻辑将在setup
方法中实现,包括data
、watch
、computed
、methods
、hooks
,并且不再有this;
vue3.x setup
方法在组件生命周期内只执行一次,不会重复执行,相比vue2.x中基于OPTIONS
配置的方式,vue3.x基于组合式API的方式语义没有2.x清晰,2.x中data
、methods
、computed
、watch
等都通过不同的scope区分开,看起来很清晰,3.x都放在setup
方法中,对代码组织能力会有更高的要求。
代码复用:
1 import { reactive, ref, getCurrentInstance } from 'vue' 2 3 const setupTest = function() { 4 const { ctx } = getCurrentInstance() 5 const propsMsg = reactive({ 6 msg: 'Hello', 7 name: 'Jiangzi', 8 age: 18 9 }) 10 const myInput = ref() 11 const formRef = ref() 12 13 return { 14 ctx, 15 propsMsg, 16 myInput, 17 formRef 18 } 19 } 20 21 export default setupTest
引入:
1 <template> 2 <div class="home"> 3 <HelloWorld :propsMsg="propsMsg"> 4 <template #backBtn> 5 <router-link to="/"> 6 <el-button type="primary" plain>Home</el-button> 7 </router-link> 8 </template> 9 </HelloWorld> 10 <h2>HelloWorld Input</h2> 11 <input v-model="myInput" /> 12 </div> 13 </template> 14 15 <script> 16 import setupTest from '@/composition/setup' 17 import HelloWorld from '@/components/HelloWorld.vue' 18 import { provide } from 'vue' 19 export default { 20 name: 'Setup', 21 components: { 22 HelloWorld 23 }, 24 setup() { 25 // const formRef = ref() 26 const { 27 propsMsg, 28 myInput 29 } = setupTest() 30 provide('provideData', 'provide data from setup') 31 return { 32 propsMsg, 33 myInput 34 } 35 } 36 }
四、router路由
获取路由配置项:
this.$router.options.routes
当前路由组件名:
this.$router.currentRoute.value.name
1 <script> 2 export default { 3 name: 'ElementNav', 4 data() { 5 return { 6 menuList: [], 7 activeName: '' 8 } 9 }, 10 created() { 11 this.$router.options.routes.forEach((item) => { 12 if (item.desc === 'element-plus') { 13 this.menuList = item.children 14 } 15 }) 16 // console.log(this.menuList) 17 let currentRouteName = this.$router.currentRoute.value.name 18 // console.log(currentRouteName) 19 this.activeName = currentRouteName 20 }, 21 mounted() {}, 22 methods: { 23 menuClick(item) { 24 this.activeName = item.name 25 }, 26 handleOpen(key) { 27 // console.log(key, keyPath) 28 if (key) { 29 this.$router.push({ name: key }) 30 } 31 }, 32 handleClose(key) { 33 if (key) { 34 this.$router.push({ name: key }) 35 } 36 } 37 } 38 } 39 </script>
项目配置页路由
1 // eslint-disable-next-line 2 import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router' 3 import Home from '@/views/Home.vue' 4 const Test = () => import('@/views/Test.vue') 5 const Setup = () => import('@/views/Setup.vue') 6 const ElementIndex = () => import('@/views/element/ElementIndex.vue') 7 const ElementHome = () => import('@/views/element/ElementHome.vue') 8 const ElementForm = () => import('@/views/element/ElementForm.vue') 9 const ElementTree = () => import('@/views/element/ElementTree.vue') 10 const ElementUpload = () => import('@/views/element/ElementUpload.vue') 11 const ElementTable = () => import('@/views/element/ElementTable.vue') 12 const ElementPages = () => import('@/views/element/ElementPages.vue') 13 const ElementCarousel = () => import('@/views/element/ElementCarousel.vue') 14 const ElementCalendar = () => import('@/views/element/ElementCalendar.vue') 15 const ElementTransfer = () => import('@/views/element/ElementTransfer.vue') 16 17 const routes = [ 18 { 19 path: '/', 20 name: 'Home', 21 component: Home 22 }, 23 { 24 path: '/test', 25 name: 'Test', 26 // route level code-splitting 27 // this generates a separate chunk (about.[hash].js) for this route 28 // which is lazy-loaded when the route is visited. 29 component: Test 30 }, 31 { 32 path: '/setup', 33 name: 'Setup', 34 component: Setup 35 }, 36 { 37 path: '/element', 38 name: 'ElementIndex', 39 component: ElementIndex, 40 redirect: '/element/home', 41 desc: 'element-plus', 42 children: [ 43 { 44 path: '/element/home', 45 name: 'ElementHome', 46 component: ElementHome, 47 desc: '基础组件', 48 icon: 'el-icon-office-building' 49 }, 50 { 51 path: '/element/form', 52 name: 'ElementForm', 53 component: ElementForm, 54 desc: 'Form表单', 55 icon: 'el-icon-message' 56 }, 57 { 58 path: '/element/tree', 59 name: 'ElementTree', 60 component: ElementTree, 61 desc: '树形控件', 62 icon: 'el-icon-grape' 63 }, 64 { 65 path: '/element/upload', 66 name: 'ElementUpload', 67 component: ElementUpload, 68 desc: '上传组件', 69 icon: 'el-icon-upload2' 70 }, 71 { 72 path: '/element/table', 73 name: 'ElementTable', 74 component: ElementTable, 75 desc: 'Table表格', 76 icon: 'el-icon-s-grid' 77 }, 78 { 79 path: '/element/pages', 80 name: 'ElementPages', 81 component: ElementPages, 82 desc: 'pages分页', 83 icon: 'el-icon-folder-opened' 84 }, 85 { 86 path: '/element/carousel', 87 name: 'ElementCarousel', 88 component: ElementCarousel, 89 desc: '走马灯', 90 icon: 'el-icon-picture' 91 }, 92 { 93 path: '/element/calendar', 94 name: 'ElementCalendar', 95 component: ElementCalendar, 96 desc: '日历', 97 icon: 'el-icon-date' 98 }, 99 { 100 path: '/element/transfer', 101 name: 'ElementTransfer', 102 component: ElementTransfer, 103 desc: '穿梭框', 104 icon: 'el-icon-d-arrow-right' 105 } 106 ] 107 } 108 ] 109 110 const router = createRouter({ 111 // history: createWebHistory(process.env.BASE_URL), // HTML5模式路由 112 history: createWebHashHistory(process.env.BASE_URL), // 哈希路由 113 routes 114 }) 115 116 export default router
五、vuex状态管理
1. 提交
1 import { createStore } from 'vuex' 2 3 export default createStore({ 4 state: { 5 homeInput: '' 6 }, 7 mutations: { 8 setHomeInput(state, data) { 9 state.homeInput = data 10 } 11 }, 12 actions: {}, 13 modules: {} 14 })