Vue2和Vue3常用代码上的直接对比:
1.项目创建命令
vue2:vue create 项目名 -> cd 项目名 -> npm run serve
vue3:npm create vue@latest -> cd 项目名 -> npm run dev
## vue3具体配置
## 配置项目名称
√ Project name: vue3_test
## 是否添加TypeScript支持
√ Add TypeScript? Yes
## 是否添加JSX支持
√ Add JSX Support? No
## 是否添加路由环境
√ Add Vue Router for Single Page Application development? Yes
## 是否添加pinia环境
√ Add Pinia for state management? No
## 是否添加单元测试
√ Add Vitest for Unit Testing? No
## 是否添加端到端测试方案
√ Add an End-to-End Testing Solution? » No
## 是否添加ESLint语法检查
√ Add ESLint for code quality? Yes
## 是否添加Prettiert代码格式化
√ Add Prettier for code formatting? No
2.初始vue文件创建
Vue2(选项式api):
<template>
<div></div>
</template>
<script>
export default {
props: {}, // 传递的组件参数
data() {
return {
// 数据
};
},
created() {},
methods: {}, // 初始化触发函数
mounted() {}, // 挂载后触发函数
watch: {}, // 监听
components: {}, // 组件列表
computed:{}, // 计算属性
};
</script>
<style></style>
Vue3(组合式api):
<template>
<div></div>
</template>
<script setup>
// ref 定义响应式变量 JS中操作数据需要:xxx.value,但模板中不需要.value
// reactive 定义响应式对象 不用.value 对象中包含ref变量,则自动解析无需.value
// toRefs 与 toRef功能一致,但toRefs可以批量转换。响应式赋值对象需要添加,否则无法继承响应式 let NewSum = toRef(sum)
// computed 计算属性,和Vue2中的computed作用一致
// watch 数据监听,需指定监听对象
// watchEffect 数据监听,无需明确监视数据,函数中调用哪些属性就监听哪些属性
import { ref,reactive,toRefs,toRef,computed,watch,watchEffect} from "vue";
// 数据
let sum = ref(0)
// 普通方法
function addSum(){
sum.value += 1
}
// toRefs 与 toRef
let person = reactive({name:'张三', age:18, gender:'男'})
// 通过toRefs将person对象中的n个属性批量取出,且依然保持响应式的能力
let {name,gender} = toRefs(person)
// 通过toRef将person对象中的gender属性取出,且依然保持响应式的能力
let age = toRef(person,'age')
// computed
let firstName = ref('zhang')
let lastName = ref('san')
// 计算属性——只读取,不修改
let fullName = computed(()=>{
return firstName.value + '-' + lastName.value
})
// 计算属性——既读取又修改
let fullName = computed({
// 读取
get(){
return firstName.value + '-' + lastName.value
},
// 修改
set(val){
console.log('有人修改了fullName',val)
firstName.value = val.split('-')[0]
lastName.value = val.split('-')[1]
}
})
let person = reactive({
name:'张三',
age:18,
car:{
c1:'奔驰',
c2:'宝马'
}
})
// watch(deep:深度监视,immediate:第一次就监听),reactive定义的对象默认开启深度监视
const stopWatch = watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{deep:true,immediate:true})
// watch:监视多个数据
const stopWatch = watch([()=>person.name,person.car],(newValue,oldValue)=>{
console.log('person.car变化了',newValue,oldValue)
},{deep:true})
// stopWatch() 暂停监听
// watchEffect 无需明确监视数据,函数中调用哪些属性就监听哪些属性
const stopWatch = watchEffect(()=>{
if(person.son > 20){
console.log('你已经20岁了')
stopWatch()
}
})
</script>
<style></style>
3.生命周期
Vue2
的生命周期
创建阶段:
beforeCreate
、created
挂载阶段:
beforeMount
、mounted
更新阶段:
beforeUpdate
、updated
销毁阶段:
beforeDestroy
、destroyed
<template>
<div></div>
</template>
<!-- vue2写法 -->
<script>
export default {
beforeCreate(){
console.log('组件创建之前');
},
created(){
console.log('组件创建完成');
},
beforeMount(){
console.log('组件挂载之前');
},
mounted(){
console.log('组件挂载完成');
},
beforeUpdate(){
console.log('组件更新之前');
},
updated(){
console.log('组件更新完成');
},
beforeDestroy(){
console.log('组件销毁之前');
},
destroyed(){
console.log('组件销毁完成');
}
}
</script>
Vue3
的生命周期
创建阶段:
setup
挂载阶段:
onBeforeMount
、onMounted
更新阶段:
onBeforeUpdate
、onUpdated
卸载阶段:
onBeforeUnmount
、onUnmounted
<template>
<div></div>
</template>
<!-- vue3写法 -->
<script lang="ts" setup>
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
// 生命周期钩子
onBeforeMount(()=>{
console.log('挂载之前')
})
onMounted(()=>{
console.log('挂载完毕')
})
onBeforeUpdate(()=>{
console.log('更新之前')
})
onUpdated(()=>{
console.log('更新完毕')
})
onBeforeUnmount(()=>{
console.log('卸载之前')
})
onUnmounted(()=>{
console.log('卸载完毕')
})
</script>
4.组件之间的通信
vue2:
-
props(父组件 -> 子组件)
// 父组件内的子组件(注意传递名称不要跟子组件内data的数据名称重叠) <Child :count="count" :userInfo="userInfo" :changeCount="changeCount"></Child> // 子组件内接收: 数组格式:props: ['count', 'userInfo', 'changeCount'], 对象格式:props:{ count:{ type:Number, // 类型 required:true, // 必填 default:0, // 默认值 // required 和 default 是互斥的,如果必填还设置默认值没有意义, }, userInfo:{ type:Object, // 类型 default(){ return {name:'老刘',age:22} } } } // 子组件内直接使用: <template> <div>{{count}}</div> </template>
-
$emit(子组件 -> 父组件)
// 子组件,使用点击事件或方法触发$emit('父组件接收的方法名',传递的参数) <button @click="$emit('changeCount',666)">修改父组件的count,并携带参数</button> methods(){ click(){ this.$emit('changeCount',666) } } // 父组件 <Child @changeCount="changeCount"></Child> methods(){ changeCount(value){ console.log('子组件传递的数据,可赋值到父组件中') } }
-
$bus(全局事件总线)
// 在main.js中设置$bus new Vue({ render: h => h(App), beforeCreate() { Vue.prototype.$bus = this//注册全局事件总线 } }).$mount('#app') // 组件之间使用(发送数据$bus.$emit('发送函数名称',传递参数)): <button @click="$bus.$emit('receiveParams', 25)">传给Child2参数</button> // 组件之间使用(接收数据this.$bus.$on('接收函数名称','触发函数')): export default { data() { return { sum: 0, }; }, mounted() { this.$bus.$on("receiveParams", this.receiveParams); }, methods: { receiveParams(params) { this.sum = params }, }, };
-
v-model(父子组件间的数据同步,双向绑定)
// 父组件 v-model = v-model:value(默认) <Child v-model="keyword"> </Child> data() { return { keyword: "abc", }; }, // 子组件 <input type="text" :value="value" @input="changeValue"> props: ['value'], // value就是v-model:value中的,可以自定义名称如:v-model:abc,v-model:efg methods: { changeValue(e) { this.$emit('input', e.target.value); } } //条件: 必须实现 1. 绑定:value值 2.绑定@input事件
-
.sync(父子组件间的数据同步,双向绑定)
// 父组件 <Child :msg.sync="string"></Child> data() { return { string: "我爱你", }; }, // 子组件 <button @click="changeParentMsg">修改父组件传过来的数据</button> props: ['msg'], methods: { changeParentMsg() { this.$emit('update:msg', "666") } } //条件: 必须实现 1.:msg="msg" 给子组件绑定数据,用于展示 2.@update:msg="changeMsg" 绑定自定义事件 3.这里 必须使用 @update:msg 冒号后面跟绑定的数据名称 4.这里的msg是v-bind绑定的属性
-
$ refs,$ chidren,$ parent(获取到组件实例、父组件、所有子组件)
// 父组件 <Child ref="son1"></Child> 在子组件中声明ref标识 mounted(){ console.log(this.$refs.son1.msg); 挂载后可获取子组件数据,created阶段无法获取$refs console.log(this.$parent); 获取父组件 console.log(this.$children); 获取所有子组件 } // 子组件 export default { data(){ return{ msg:666 } }, }
-
Provide、Inject(爷组件 -> 孙组件)
// 爷组件 <template> <div> <button @click="changeMsg">祖组件触发</button> <h1>祖组件</h1> <parent></parent> </div> </template> <script> import parent from './parent.vue'; export default { data(){ return{ obj:{ name:'JavaScript', }, developer:'布兰登·艾奇', year:1995, update:'2021年06月', } }, provide(){ return { obj: this.obj, // 方式1.传入一个可监听的对象 developerFn:() => this.developer, // 方式2.通过 computed 来计算注入的值 year: this.year, // 方式3.直接传值 app: this, // 方式4. 提供祖先组件的实例 缺点:实例上挂载很多没有必要的东西 比如:props,methods。 } }, components: { parent, }, methods:{ changeMsg(){ this.obj.name = 'Vue'; this.developer = '尤雨溪'; this.year = 2014; this.update = '2021年6月7日'; }, }, } </script> // 父组件 <template> <div class="wrap"> <h4>子组件(只做中转)</h4> <child></child> </div> </template> <script> import child from './child.vue'; export default { components:{ child, }, } </script> // 子组件 <template> <div> <h5>孙组件</h5> <span>名称:{{obj.name}}</span> | <span>作者:{{developer}}</span> | <span>诞生于:{{year}}</span> | <span>最后更新于:{{this.app.update}}</span> </div> </template> <script> export default { computed:{ developer(){ return this.developerFn() } }, inject:['obj','developerFn','year','app'], } </script>
-
VueX
1.安装vuex:npm install vuex@3 --save 2.创建store目录下的store.js文件: import Vue from "vue"; import Vuex from 'vuex'; Vue.use(Vuex); export default new Vuex.Store({ state:{ session_key:'111' }, // 数据源 mutations:{ getSessionKey(){ console.log(state.session_key) } }, // 方法 actions:{}, // 异步处理(函数) getters:{}, // 计算属性 modules //模块化 }) 3.在main.js中挂载: import store from './store' new Vue({ store, render: h => h(App), }).$mount('#app') 4.在组件中调用(取值可直接通过$store.state或...mapState(['xxx']),赋值VueX,则需要在VueX的mutations中定义方法并接收数据,组件再通过...mapMutations(['xxx']),调用xxx方法赋值回VueX): <div> {{$store.state.session_key}} </div> <script> import {mapState,mapMutations} from 'vuex' export default { name: "App", data() { return { }; mounted(){ console.log(this.$store.state.session_key) }, methods:{ ...mapMutations(['getSessionKey']) }, computed:{ ...mapState(['session_key']) }, }; }; </script>
vue3:
-
defineProps(子组件用defineProps来接收父组件传递的数据)(父组件 -> 子组件)
// 父组件 <template> <div class="father"> <h3>父组件,</h3> <h4>我的车:{{ car }}</h4> <h4>儿子给的玩具:{{ toy }}</h4> <Child :car="car" :getToy="getToy"/> </div> </template> <script setup lang="ts" name="Father"> import Child from './Child.vue' import { ref } from "vue"; // 数据 const car = ref('奔驰') const toy = ref() // 方法 function getToy(value:string){ toy.value = value } </script> // 子组件 <template> <div class="child"> <h3>子组件</h3> <h4>我的玩具:{{ toy }}</h4> <h4>父给我的车:{{ car }}</h4> <button @click="getToy(toy)">玩具给父亲</button> </div> </template> <script setup lang="ts" name="Child"> import { ref } from "vue"; const toy = ref('奥特曼') defineProps(['car','getToy']) </script>
-
自定义事件(子组件用defineEmits来接收父组件传递的数据)(子组件 -> 父组件)
// 父组件($event是子组件自定义事件传递的参数) <Child @send-toy="toy = $event"/> <Child @send-toy="saveToy"/> // 子组件 const emit = defineEmits(['send-toy']) // 接收父组件函数 emit('send-toy',666) // 子组件调用函数并传参,就会触发父组件的saveToy函数
-
mitt(全局事件总线)
1.安装mitt: npm i mitt 2.新建文件:src\utils\emitter.ts 3.编辑emitter.ts: import mitt from 'mitt' const emitter = mitt() export default emitter 4.组件绑定事件到emitter(on绑定 emit触发 off解绑 all全部): import emitter from '../utils/emitter' // emitter.on('绑定事件名称',(触发事件传递的参数)=>{//逻辑}) emitter.on('send-toy',(value)=>{ toy.value = value }) // 在组件卸载时解绑send-toy onUnmounted(()=>{ emitter.off('send-toy') // emitter.all.clear() 全部解绑 }) 5.组件调用emitter绑定的事件: import emitter from '../utils/emitter' <button @click="emitter.emit('send-toy',666)">触发绑定的函数并传递参数</button>
-
pinia(类似VueX)
1.安装pinia: npm install pinia 2.在src/main.js中引入: import { createApp } from 'vue' import App from './App.vue' import { createPinia } from 'pinia' const app = createApp(App) const pinia = createPinia() app.use(pinia) app.mount('#app') 3.创建store文件夹,创建对应的store: 如果是一个存储用户信息组件,则在store文件夹下创建对应的user.ts,里面保存用户信息,每个组件都能读取,写入它。 4.编辑user.ts: // 引入defineStore用于创建store import {defineStore} from 'pinia' // 定义并暴露一个store(可按文件命名useUserStore,defineStore的第一个参数也按文件命名,防止混淆) export const useUserStore = defineStore('user',{ // 方法 actions:{ getSessionKey(){ return this.session_key } }, // 数据 state(){ return{ session_key:'666' } }, // 计算属性 getters:{} }) 5.组件中调用: // 引入对应的useXxxxxStore import {useUserStore} from '@/store/User' // 调用useXxxxxStore得到对应的store const userStore = useUserStore() <template> <h2>当前session_key为:{{ userStore.session_key }}</h2> </template> <script setup> function getSessionKey(){ userStore.getSessionKey() } </script> 6.修改store的数据: 第一种修改方式,直接修改 countStore.sum = 666 第二种修改方式:批量修改 countStore.$patch({ sum:999, school:'atguigu' }) 第三种修改方式:借助action修改(action中可以编写一些业务逻辑) import { defineStore } from 'pinia' export const useUserStore = defineStore('user', { actions: { setSesstionKey(value:number) { this.session_key = value } }, }) // 组件中再调用方法 const userStore = useUserStore() userStore.setSesstionKey(n.value) 7.使用storeToRefs将store中的数据转为ref对象,方便在模板中使用(为什么用storeToRefs而不是toRefs,storeToRefs只会把数据进行转换,而toRefs会把全部东西转换位ref对象) <template> <div class="count"> <h2>当前求和为:{{sum}}</h2> </div> </template> <script setup lang="ts" name="Count"> import { useCountStore } from '@/store/count' /* 引入storeToRefs */ import { storeToRefs } from 'pinia' /* 得到countStore */ const countStore = useCountStore() /* 使用storeToRefs转换countStore,随后解构 */ const {sum} = storeToRefs(countStore) </script> 8.$subscribe:通过 store 的 $subscribe() 方法侦听 state 及其变化 talkStore.$subscribe((mutate,state)=>{ console.log('LoveTalk',mutate,state) })
5.自定义Hook
-
什么是
hook
?—— 本质是一个函数,把setup
函数中使用的Composition API
进行了封装,类似于vue2.x
中的mixin
。 -
自定义
hook
的优势:复用代码, 让setup
中的逻辑更清楚易懂。// 组件内使用hook <template> <h2>当前宠物为:{{dogList[0]}}</h2> </template> <script setup lang="ts"> import useDog from './hooks/useDog' let {dogList,addDog} = useDog() </script> // 在src下创建hook目录,并创建useDog.ts,命名以use+组件的主要逻辑命名就行,用户信息可以写useUser.ts import {ref,reactive,onMounted,toRef} from 'vue' import axios from 'axios' import useSum from './useSum' // https://dog.ceo/api/breed/pembroke/images/random export default function(){ let dogList = reactive([ 'https://images.dog.ceo/breeds/pembroke/n02113023_15926.jpg' ]) async function addDog(){ try{ let result = await axios.get("https://dog.ceo/api/breed/pembroke/images/random") dogList.push(result.data.message) }catch(error){ alert(error) } } return {dogList,addDog} } // 相当于把组件的逻辑都放到一个文件下,方便其他组件复用代码, 让setup中的逻辑更清楚易懂。类似于vue2的mixin
6.路由
6-1【安装vue-router】
vue2:npm install vue-router@3
vue3:npm install vue-router@4
6-2【vue-router配置环境】
vue2:
第一步:在src目录下新建router文件夹,创建index.js
第二步:在index.js文件中导入路由并使用
import Vue from 'vue'
import VueRouter from 'vue-router'
import Index from './components/Index.vue'
Vue.use(VueRouter)
const routes = [{
path:'/',
name:'Index',
component:Index,
}]
const router = new VueRouter({
routes
})
export default router
第三步:在main.js放入到app全局中
import router from './router'
new Vue({
router,
render:h => h(App)
}).$mount('#app')
vue3:
第一步:在src目录下新建router文件夹,创建index.ts
第二步:在index.ts文件中导入路由并使用
import { createRouter,createWebHashHistory,createWebHistory } from "vue-router";
import Home from './components/Home.vue'
const router = createRouter({
history:createWebHashHistory(), //路由模式
routes:[
{
path:'/home',
name:"home",
component:Home
}
]
})
export default router
第三步:在main.ts放入到app全局中
import { createApp } from 'vue'
import router from './router'
import App from './App.vue'
const app = createApp(App)
app.use(router)
app.mount('#app')
6-3【嵌套路由】
vue2:
import Index from './components/Index.vue'
import Details from './components/Details.vue'
const routes = [{
path:'/',
name:'Index',
component:Index,
children:[
{
name:'details'
path:'details', // 不用写/details
component:Details
}
]详情
}]
完整路径:
<router-link to='/home/details'>跳转详情</router-link>
<router-link :to='{path:"/home/details"}'>跳转详情</router-link>
<router-link :to="{name:'details'}">跳转详情</router-link>
this.$router.push({ name: 'details', params: { id: row.id } });
this.$router.push({ path: '/home/details', query: { id: row.id } });
vue3:
import Home from './components/Home.vue'
import Details from './components/Details.vue'
const router = createRouter({
history:createWebHashHistory(), //路由模式
routes:[
{
path:'/home',
name:"home",
component:Home,
children:[
{
name:'detail',
path:'detail',
component:Detail,
}
]
}
]
})
完整路径:
<router-link to='/home/details'>跳转详情</router-link>
<router-link :to='{path:"/home/details"}'>跳转详情</router-link>
<router-link :to="{name:'details'}">跳转详情</router-link>
import { useRouter } from "vue-router";
const router = useRouter()
// 字符串路径
router.push('/home/details')
// 带有路径的对象
router.push({ path: '/home/details' })
// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'details', params: { username: 'eduardo' } })
// 带查询参数,结果是 /register?plan=private
router.push({ path: '/home/details', query: { plan: 'private' } })
6-4【路由的props配置】
作用:让路由组件更方便的收到参数(可以将路由参数作为props
传给组件)
{
name:'xiang',
path:'detail/:id/:title/:content',
component:Detail,
// props的对象写法,作用:把对象中的每一组key-value作为props传给Detail组件
// props:{a:1,b:2,c:3},
// props的布尔值写法,作用:把收到了每一组params参数,作为props传给Detail组件,且path一定要
带有/:id/:title/:content?
// 如果使用props配置true的方法,那么传参必须使用params方式才奏效(注意如果是query参数不会奏效的)
// props:true
// props的函数写法,作用:把返回的对象中每一组key-value作为props传给Detail组件
props(route){
return route.query
}
}
6-5【replace属性】
-
作用:控制路由跳转时操作浏览器历史记录的模式。
-
浏览器的历史记录有两种写入方式:分别为
push
和replace
:push
是追加历史记录(默认值)。replace
是替换当前记录。
-
开启
replace
模式:<RouterLink replace .......>News</RouterLink>
6-6【路由重定向】
-
作用:将特定的路径,重新定向到已有路由,默认/重定向到/home
-
具体编码:
{ path:'/', redirect:'/home' }
7.常用的vue2.x,3.x开发组件UI库:
组件大全:https://www.vue3js.cn/
element-ui:https://element.eleme.cn/#/zh-CN/component/installation
vant-ui:https://vant-ui.github.io/vant/#/zh-CN
vuetify:https://vuetifyjs.com/zh-Hans/getting-started/installation/#sass
naive-ui:https://www.naiveui.com/zh-CN/os-theme
iview:https://v2.iviewui.com/docs/guide/install