Vue2和Vue3常用代码上的直接对比

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的生命周期

创建阶段:beforeCreatecreated

挂载阶段:beforeMountmounted

更新阶段:beforeUpdateupdated

销毁阶段:beforeDestroydestroyed

<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

挂载阶段:onBeforeMountonMounted

更新阶段:onBeforeUpdateonUpdated

卸载阶段:onBeforeUnmountonUnmounted

<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属性】
  1. 作用:控制路由跳转时操作浏览器历史记录的模式。

  2. 浏览器的历史记录有两种写入方式:分别为pushreplace

    • push是追加历史记录(默认值)。
    • replace是替换当前记录。
  3. 开启replace模式:

    <RouterLink replace .......>News</RouterLink>
    
6-6【路由重定向】
  1. 作用:将特定的路径,重新定向到已有路由,默认/重定向到/home

  2. 具体编码:

    {
        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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值