前言
这里将自己所了解到的vue3与vue2在使用的时候的区别进行整合一下,因为对于项目来说,每个人书写的结构不同,所以对于我的分享而言,可能不适合你去ctrl+c\v去使用,这可能需要你去花时间去看,对此我表示抱歉O.O
,在vue2中会将所有vue中的模块全部导入进来,而在vue3里面,它是按需导入的,也就是如果你需要使用那个功能,你就引入它,如果不使用,就不引入,这样就不会引入一些没有作用的功能模块了。
从初始化vue3开始
步骤:(使用vue-cli初始化)
// 在终端或者命令行用这个命令初始化项目,后面的项目名称为自己输入的项目名称
vue create 项目名称
// 在输入上面的命令之后,从前到后会出现以下选择:
Please pick a preset // 选择一个预设,如果之前自己初始化过项目并保存为预设
// 的时候这里就会有选择,最后一个选项为手动配置项目
Check the features needed for your project // 选择初始化的项目里面会包含
// 那些功能
Choose a version of Vue.js that you want to start the project with
// 选择vue的版本
Pick a linter / formatter config // 使用那种写代码的规范对自己写的代码进行
// 约束,代码风格如果与选择的风格不一致的
// 话就会报错来出现警告
Pick additional lint features // 选择格式化的功能
Where do you prefer placing config for Babel, ESLint, etc.?
// 将第二步选择功能安装的插件存放在那个文件下面
Save this as a preset for future projects?
// 上面的配置是否保存为预设,如果保存,再次初始化的时候就可以在第一步进行选择
初始化的项目与vue2的差别
// 对于main.js
// 1.在vue3里面,存在模块化的设计,所以存在按需导入,减少的打包的压缩体积,加
// 快下载的速度
// 2.在这里,createApp这个模块是用来创建根实例的
import { createApp } from "vue"
import App from "./App.vue"
import router from "./router"
// 3.挂载使用mount的方法而不是$mount
createApp(App).use(router).mount('#app')
// 对于路由router里面的index.js
// 4.createRouter用来创建一个路由
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '../views/Home.vue'
const routers = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue'),
},
];
const router = createRouter({
// 5.在执行createRouter这个函数的时候需要带上一个history的选项,
// 它存在两种模式,一种是createWebHashHistory哈希模式,
// 一种是createWebHistory历史记录模式
history: createWebHashHistory(),
routers,
});
export default router;
router:使用vueRouter创建的唯一实例,获取到router,那么实例上的方法都可以使用
route:表示当前所处的页面路由的信息
name:这个属性的值唯一(可选)
模板语法与指令
// 6.首先在模板中可以不需要唯一的跟标签进行包裹
<template>
<span>2</span>
<span>3</span>
</template> // 这样书写模板不会报错
// 7.v-model指令使用的改变
// 变化1:绑定默认的modelValue的属性和update:modelValue的事件
// 变化2:v-model是可以传递参数的,传递的参数是绑定的modelValue属性的替代品
// 变化3:可以绑定多个v-model的值
// 使用el-input组件:
<template>
<el-input v-model="msg" />
</template>
<script>
export default {
data() {
return {
msg: "123"
}
}
}
</script>
// el-input组件的内部:
<template>
<input type="text" :value="modelValue" @input="trigger" />
</template>
<script>
export default {
name: "el-input",
props: {
modelValue: {
type: String,
required: true
}
}
methods: {
trigger(e) {
this.$emit("update:modelValue", e.target.value)
}
}
}
</script>
注意:vue2中vue实例做的事情在vue3里面被实例的app来代替
对于v-model和组件的model选项的区别:v-model使用在组件内部用于页面上显示的数据与自己在js中定义的数据之间进行相联系,而组件的model的选项适用于组件与组件之间数据的传递,注意,"v-model"指令实际上是基于"model"选项实现的。
事件
// 8.事件的触发
// 在vue2里面,在父组件中直接写在子组件上的事件,如果在子组件里面做一些处理
//(使用$emit触发或者添加native的修饰符)是不会触发的,但是在vue3中,是会触
// 发的,这样就会存在一个在父组件中定义的事件会不清楚它是父组件的事件还是子组
// 件上面的事件,所以在vue3中,增加了一个emits的配置项,这个配置项中定义的事
// 件就是子组件特有的事件,只能够由子组件触发,触发的方式通过$emit(事件名)来
// 完成。
// 在父组件中使用el-button组件:
<template>
<el-button @click="fn" />
</template>
<script>
export default {
methods: {
fn() {
console.log(11)
}
}
}
</script>
// el-button组件的内部:
<template>
<button type="text" @click("click") /> // 触发自己所霸占的事件
</template>
<script>
export default {
name: "el-button",
emits: ['click'] // 将父组件中绑定给子组件click事件占为己有
}
</script>
问题:在vue2中,为什么v-if和v-for不建议同时作用于同一个标签
回答:因为v-for的优先级高于v-if,如果同时使用的话,那么可能的情景就是如果条件满足,我就去循环,但是v-for的优先级高,就会出现不管满不满足都会循环,从而达不到节约性能的目的,所以在vue2中不建议同时作用于同一个标签,但是在vue3中v-if的优先级高一点,那么如果条件不满足,就不会去循环,从而达到了节约性能的目的
组合式API
说明: vue的语法有两种,一种是vue2的语法,叫做选项式api的语法(就是有template、data、methods这种配置项的语法),一种是vue3的语法,叫做组合式api的语法(setup函数的语法),为了能够更快的了解vue3语法,所以它兼容100%的vue2的语法,毕竟vue2的语法相对而言容易接受一点。
作用: 提供一些可以按需加载的模块来替代原来选项式API的能力并且可以根据自己的业务逻辑进行组合,但是组合式API必须在setup配置项中使用,这个配置项替代了vue2中的created和beforecreate两个生命周期函数
。
setup:
// 9.setup函数的初始:
// 在vue3中,定义响应式数据使用ref函数,在使用的时候,将需要变成响应式的数据
// 传递进去就可以了,但是注意,如果需要取到响应式数据的值需要 .value才可以,
// 否则会取不到的
// 在组合式api中,组合封装的能力、共享逻辑的能力很强大,假设我有两个相同的功
// 能,但是传递的值不同,那么我就可以将这两个功能提取出来进行封装,在需要的
// 地方我进行导入,在vue2中这样写可能会显得十分杂乱,但是在vue3中所有的逻辑
// 都写在setup配置项里面,那么我进行提取、封装、注释、引入这些操作不会使我的
// 页面更加凌乱,反而会使页面的代码逻辑显得清楚的多。(例如下面的我需要点击
// h1标签的时候将里面的数值进行增加1的操作,我存在两个h1的标签,那么这个操作
// 我会重复两次,这样重复的操作我可以进行封装处理)
// 注意:对于组合式的API,他都必须在setup函数内使用
// App.js
import { ref } from 'vue';
// 功能的封装
export function feature(initValue = 0) {
const data = ref(initValue);
const fn = () => {
data.value += 1;
};
return [data, fn];
}
export default {};
// App.vue
<template>
<nav>
<h1 @click="increase">{{ a }}</h1>
<h1 @click="increaseB">{{ b }}</h1>
</nav>
</template>
<script>
// 导入封装的逻辑
import { feature } from '@/App';
export default {
setup() {
// 功能a
const [a, increase] = feature(1);
// 功能b
const [b, increaseB] = feature(2);
// 在setup中定义的数据需要在模板中使用的时候setup函数就需要返回值
// 当返回值是一个函数的时候这个函数的功能是render函数
// 当返回值是非函数的时候返回的结果提供给模板去使用
return {
a, increase, b, increaseB,
};
},
};
</script>
// 10.setup函数的参数:
<script>
export default {
// 因为在vue3的API的使用,它主要是使用的组合式API,对于组合式API,它是
// 将全部的配置写在setup函数中,所以对于vue3的组件,它只需要一个setup
// 函数的配置就足够了,但是vue2和vue3有一个区别就是没有this了,在vue2
// 中访问数据是使用this的,但是vue3里面不存在,为了在vue3中不使用this
// 也可以访问到数据,它就将数据作为setup函数的参数来供你使用。
setup(props, context) {
// 对于context参数,它是一个对象,对象里面存在attrs、slots、emit
// 三个属性。
// 如果需要使用它们直接使用props、context.attrs、context.slots、
// context.emit就可以获取或者是触发了
}
}
</script>
// 11.setup函数的简写形式:
// 给script标签添加一个setup的属性,添加这个属性之后,会让整个Script标签
// 变成一个setup的函数,script标签内部的区域就是setup函数的区域,然后对于
// 这个区域,需要注意以下几点:
// 注意1:可以省略不写export default
// 注意2:导入的组件会自动注册,可以直接使用
// 注意3:setup函数的参数会失效,但是可以通过defineProps、defineEmits、
// useAttrs、useSlots这几个函数来替代
// 注意4:不需要return就可以提供给模板使用了
// 注意5:在这个标签里面它自己就是一个async的异步函数,所以在标签里面
// 可以直接使用await来写异步函数
<script setup>
// 在使用这些函数的时候如果是define开头就是定义的意思,给函数传递一个
// 对象,以间值对的形式定义,定义的方式跟vue2中的没什么区别,如果需要
// 拿到传递的prop、attr、触发事件的话,将这个函数的返回值用一个变量
// 将其接住,这个变量就是传递的内容对象;use开头是使用的意思,使用起
// 来跟前者没有什么区别,只是不需要传递对象最为参数了(举例)
// 这里就定义了一个a的prop属性,如果父组件给这个a传递过值的话,就会
// 被prop所接住,此时的prop就是传递的数据对象,使用prop.a就可以获取
// 到这个值了。
const prop = defineProps({
a: String
})
</script>
查看某一个库到底导入多少变量,(可以使用):
import * as 自定义名字 from "库名"
console.log(自定义名字)
在vue3中也可以书写多个script标签
响应式数据的定义:
// 12.对于定义响应式数据可以使用ref函数、reactive函数:
// 注意1:
// 通过ref来定义响应式的数据,并通过value属性来取值,在template里面不需要,
// 因为vue在模板解析的时候自动会使用value进行取值
// 注意2:(ref函数、reactive函数的使用区别)
// 从定义的数据类型上看:
// ref函数可以定义基本的数据类型,也可以定义引用类型
// reactive函数只能够定义引用数据类型
// 从使用层面上看:
// ref函数取值需要使用.value的方式来获取响应式数据的值
// reactive函数取值不需要使用.value的方式来取值
// 在使用的注意点上看:
// 使用reactive函数函数的时候不可以对数据进行结构、扩展运算符的操作,会失去
// 响应式的效果
// 而ref函数定义的响应式数据可以进行解构等操作而不丢失响应式的能力
// 13.将reactive函数定义的数据转换为ref函数定义的数据可以使用toRef函数
// (将reactive函数定义的响应式数据的某一个选项转换为ref函数的响应式
// 数据)和toRefs函数(将reactive函数定义的响应式数据的所有选项转化
// 为ref函数的响应式数据)进行操作
<script setup>
import { toRef, toRefs,reactive } from "vue"
// 定义一个reactive的响应式数据
const user = reactive({
name: ""
age: ""
})
// 将reactive数据的name属性、age属性变成ref的响应式数据
const name = toRef(user, "name")
const age = toRef(user, "age")
// 对改变的ref数据解构不会丢失其响应式的能力
const { name, age } = toRefs(user)
</script>
// 14.ref可以用来获取dom节点
<template>
// 给节点绑定一个ref的属性,在挂载之后ref绑定的这个值会拿到这个节点
<div ref="div">123</div>
</template>
<script setup>
import { ref, onMounted } from "vue"
// 初始的时候让绑定的这个节点的值为空
const div = ref(null)
// 挂载之后的这个声明周期在vue3中变成了onMounted
onMounted(() => {
// ref类型的响应式数据要通过.value的方式获取到值
console.log(div.value)
})
</script>
计算属性computed:
<script setup>
// 15.计算属性的使用
import { ref, computed } from "vue"
const a = ref(10)
const b = ref(10)
// 对于计算属性来说它在vue3中变成了一个函数,函数的参数接受一个需要计算的
// 函数,如果下一段逻辑也需要计算属性,直接再写一个computed函数就可以了
// (这个写法是基本写法)
const aAndb = computed(() => { a.value * b.value })
const aAndb = computed(() => { a.value * b.value }) // 复用
// 对象写法,具有取值器get,和存值器set,跟vue2的用法一样
const aAndb = computed(() => {
get() {
return a.value * b.value
}
set(value) {
a.value = value * 2
b.value = value * 2
}
})
// 对于计算属性来说,用法是一模一样的,在vue2中如何使用在vue3中就如何使用,
// 只不过在vue3中存在写法的差异,需要注意一下
</script>
对于vue2的混入来说,如果页面使用了多个混入的时候,可能会出现变量名的重复,导致效果的失效,但是在vue3中,对于重复使用的逻辑可以在单独的文件里面通过函数的形式来进行封装,然后将需要使用到参数将其返回出去,这样在引入的时候,我得到的函数的返回值,如果返回值是一个数组或者对象的话,我可以对其进行解构和重新赋值,以此来避免变量名的重复导致效果的失效。
在vue.config.js中,存在一个
runtimeCompiler的配置选项
,如果将他的值设置为ture的话它会在运行时使用合适的vue的版本来编译文件,从而避免了template渲染的问题而导致页面出不来
监听器watch、watcheffect
// 16.vue3中监听器多了一个watcheffect,它和watch都可以起到监听的作用,
// 但是在使用方式上存在差异,相同的是他们都是一个函数。
// watch的使用:
// watch函数它有三个参数,第一个参数是监听的属性,类型是字符串,第二个参数
// 是一个回调函数,回调函数的参数有两个,第一个参数是改变后的值,第二个参数
// 是改变前的值,第三个参数是一个对象,这个对象用来配置深度监听和是否立即执
// 行,所以如果只需要监测具体的某个属性的话,就使用watch来完成。
watch("属性名", (改变后的值,改变前的值) => {}, {
deep: true // 是否深度监听
immediate: true // 是否立即执行回调函数
})
// 对于watch来说,它可以监听一个或多个响应式数据, 一旦数据变化, 就自动
// 执行监听回调, 如果监听rective对象中的属性, 必须通过函数来指定,如果
// 监听多个数据,需要使用数组来指定,默认初始时不执行回调,当可以通过配置
// immediate未true,来指定初始时立即执行第一次,通过配置deep为true,来
// 指定深度监听,在监听多个属性的时候需要使用数组来完成
// watchEffect的使用:
// watcheffect的参数是一个函数,在这个函数中它会自动检测到使用响应式数据
// 的地方,当响应式数据发生改变的时候,该函数会立即执行一次,其次,这个函数
// 的返回值是一个函数,这个返回的函数用来取消监听(将返回的函数执行),函数
// 默认第一次执行。
const stop = watchEffect(() => {})
stop() // 执行这个返回值函数就会立即中断监听
pinia的使用(vue3的vuex)
安装:
yarn add pinia -s
配置:
// 17.在根目录下面创建一个store的文件夹用来存储响应式的数据
// 在store问价夹中添加一个index.js的文件用来初始化vuex
// index.js文件
import { createPinia } from 'pinia'
// 使用这个插件可以将vuex中的数据都同步到本地存储中去
import persistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(persistedstate)
export default pinia
// 由于pinia是一个插件,如果需要使用就需要在main.js入口文件中导入并注册
import { createApp } from 'vue'
import App from '@/App.vue'
import pinia from '@/store' // 将初始化的pinia导入
const app = createApp(App)
app.use(pinia) // 注册pinia
在使用pinia的时候,store中的一个js文件就是一个仓库(index.js初始化文件除外)
// 举例:创建一个useCookie.js文件来存储用户的信息
import { defineStore } from 'pinia' // 使用defineStore定义一个数据仓库
// 在使用的时候需要给仓库起一个名字,这个名字规范为使用use开头,并用驼峰法
// 命名,defineStore有两个参数,第一个为仓库id,这个id规范是把仓库名的use
// 去掉取剩下的部分
const useUser = defineStore('user', {
state:() => ({
cookie: null // 定义了一个数据cookie
}) // 类似于data用来定义数据
getters: {} // 类似于计算属性
actions: {
setCookie(payload){ // payload为修改的新值(载荷)
this.cookie = payload
}
} // 用来修改值的操作
persist: true // 将vuex中的数据和localstorage关联起来
})
export default useUser // 当导入useUser的时候就能够访问到这个仓库里面的数据了
使用:
// 在使用的时候需要将数据仓库先导入并执行一次(因为是一个函数),执行这个函数之
// 后就可以使用了里面的数据或者是方法了
import useCookie from '@/useCookie.js' // 引入数据仓库
const cookie = useCookie() // 执行函数一次
cookie.setCookie(1) // 改值
cookie.cookie // 取值
路由
此处省略使用
vue create 项目名
来初始化vue2和vue3两个项目的步骤
vue2的路由跳转:
// 声明式导航(使用模板跳转):
// 对于这类跳转一般使用<router-link />标签进行跳转
// 这种跳转也称为路径跳转,它可以简写,完整写法是写对象写法
<router-link to='/home' />
// <router-link />是router.push的一种模板语法的表达方式,其to属性绑定的值
// 就是push方法的参数
<router-link :to={ path: '/home' } />
// 编程式导航(脚本跳转):
<template>
<button @click=fn />
</template>
<script>
import router from '@/router' // 如果需要使用router.push的方法需要router的
// 实例,在router文件夹中导出了这个实例,导入
// 即可,这个实例保存了所有信息
export default {
methods: {
fn() {
router.push('/home') // 使用脚本跳转
}
}
}
</script>
// name跳转(没有简写,不常用):无视路径的嵌套(一般使用在多层路由嵌套的时候)
<template>
// 这个name名要与routes中写的一致
<router-link :to={ name: 'home' } /> // 声明式
<button @click=fn /> // 编程式
</template>
<script>
import router from '@/router'
export default {
methods: {
fn() {
router.push({ name: 'home' })
}
}
}
</script>
this.$router
:替代router的引入$route
: 替代router.currentRoute属性(查看当前
渲染路由信息)$route.matched
:记录路由跳转的完整过程$route.meta
:记录自己定义的数据(路由标题、图标、权限等)
$route(value) { // $route的参数会拿到当前路由的所有信息
document.title = value.meta.title // meta属性的值是一个对象、
}
// 前置守卫:router.beforeEach()
// 后置守卫:router.afterEach()
router.beforeEach(function(to, from, next){ // 去哪里,来自哪里,能否通过
// 在路由的meta属性中如果存在auth(权限)的话表示有权限,就调用next方法
// 允许跳转,否则就跳转到指定的页面去(一般登陆页面会设置)
if(to.matched.auth) {
next('/login') // next方法参数可以简写,使用对象写法和name属性
} else {
next()
}
})
// 路径跳转的params参数如何传递:(页面刷新参数不丢失)
// 要将参数传递到那个页面里面,就需要在传递到的页面的路由配置的路径后面用:参数名的
// 方式进行拼接
// home页面的路由的路径
path: /home/:id/:uid // 这里定义了两个参数id和uid
// 跳转传递参数
<router-link :to={ path: '/home/123/456' } /> // 传递了参数id: 123和uid: 456
// 接受参数:home页面
console.log(this.$route.params) // 这里可以接收到传递的id和uid两个参数
// 路径跳转的query参数如何传递:(页面刷新参数不丢失)
// 传递参数直接在后面用query配置即可
// 跳转传递参数
<router-link :to={ path: '/home/123/456',query: {
id: 123,
uid: 456
} } /> // 传递了参数id: 123和uid: 456
// 接受参数:home页面
console.log(this.$route.query) // 这里可以接收到传递的id和uid两个参数
// name跳转的query和params参数如何传递:
<router-link :to={
name: 'home',
// 传递query参数时跟路径跳转传递和接收参数的方式一样,页面刷新不会丢失
query: {
id: 123,
uid: 456
} } ,
// 传递params参数时在跳转到指定的页面之后他会用参数自动补全路径(即使没写
// 参数具体在那个地方,但是如果不具体指明参数的位置那么页面刷新的时候参数
// 会丢失,想要参数不丢失那么就需要在路径中指明参数具体在哪个位置,用路径
// 跳转的指明参数的方式一样)
params: {
id:132,
uid:456
}
/>