VUE3版本新特性
- VUE3和VUE2的区别
- 路由的使用
- vite安装项目
- 新特性使用
1.VUE3和VUE2的区别
2020年9月18日,Vue.js
发布版3.0
版本,代号:One Piece
于 2022 年 2 月 7 日星期一成为新的默认版本! Vue3性能更高,初次渲染快55%
, 更新渲染快133%
。体积更小 Vue3.0 打包大小减少41%
。 同时Vue3
可以更好的支持TypeScript
。 vue3中文文档:Vue.js : https://cn.vuejs.org/,vue2和vue3的主要区别在于以下几点:1、生命周期函数钩子不同
beforeCreate -> setup() 开始创建组件之前,创建的是data和method
created -> setup()
beforeMount -> onBeforeMount 组件挂载到节点上之前执行的函数。
mounted -> onMounted 组件挂载完成后执行的函数
beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。
updated -> onUpdated 组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。
destroyed -> onUnmounted 组件卸载之前执行的函数。
activated -> onActivated 组件卸载完成后执行的函数
deactivated -> onDeactivated 在组件切换中老组件消失的时候执行
2、数据双向绑定原理不同
VUE2 | VUE3 |
---|---|
利用ES5的一个APIObject.defineProperty()对数据进行劫持,结合发布者订阅者模式的方式来实现的。 | 使用了ES6的Proxy API对数据代理。 |
vue3提供的proxy API代理的优势在于:
- defineProperty只能监听某个属性,不能对全对象监听
- 可以省去for…in,闭包等内容来提升效率(直接绑定整个对象即可)
- 可以监听数组,不再单独的对数组做特异性处理。可以检测到数组内部数据的变化。
3、定义变量和方法不同
vue2 | vue3 |
---|---|
在data中定义变量,在methods中创建方法 | 使用一个新的setup方法 |
vue3提供的setup方法在组件初始化构造的时候触发,使用以下三个步骤建立反应性数据:
-
从vue引入reactive
-
使用reactive方法来声明数据为响应性数据
-
使用setup方法返回响应性数据,从而template可以获取这些响应式数据。
<script> export default { setup(){// 数据和方法都写这里,更简洁const user = "username";const met = ()=>{} } } </script>
4、API类型不同
vue2 | vue3 |
---|---|
选项型api(在代码中分割不同属性:data,computed,methods等) | 组合型api(使用方法进行分隔,显得更加简便整洁) |
5、是否支持碎片
vue2 | vue3 |
---|---|
否 | 是,即可以拥有多个根节点 |
<template><div><h1></h1><div><div><h1></h1><div>
</template>
6、父子之间传参不同
vue2 | vue3 |
---|---|
父传子:子组件通过prop接收子传父:子组件中通过$emit向父组件触发一个监听方法,传递一个参数 | 使用setup()中的第二个参数content对象中有emit,只需要在setup()接收第二个参数中使用分解对象法取出emit就可以在setup方法中随意使用了。 |
7、v-if 和 v-for的优先级
VUE2 | VUE3 |
---|---|
v-for 优先于 v-if 生效 | v-if 优先于 v-for 生效 |
2.使用Vite创建项目
在VUE3中使用vite来创建项目而不再使用 vue-cli,初始化项目 ,vite
是新一代前端构建工具,官网地址:https://vitejs.cn,vite
的优势如:
-
轻量快速的热重载(
HMR
),能实现极速的服务启动。 -
对
TypeScript
、JSX
、CSS
等支持开箱即用。 -
真正的按需编译,不再等待整个应用编译完成。注意:Vite 需要 Node.js 版本 >= 12.0.0。【使用nvm安装 node 18.14.2】
注意:还需要去设置仓库地址: npm config set registry https://registry.npmmirror.com在VUE2中使用npm进行包的管理,在VUE3中pnpm进行包的管理,首先我们需要安装 pnpm
- 安装pnpm :
在磁盘上创建一个文件夹,如: vue3-demo,然后进入目录,使用cmd窗口执行命令如下:
npm i pnpm -g
-
安装vite : 执行下面命令,按照提示完成项目创建
pnpm create vite@latest 或者 yarn create vite
1.输入项目名字,默认为vue3-demo2.选择创建的项目类型,选择vue即可3.选择创建的vue项目类型, TypeScript4.启动项目
命令 : pnpm create vite@latest 执行效果如下
![1710845893295](images\1710845893295.png)- 也可以使用 : npm init vite@latest
3. 安装依赖: `进入到创建好的项目中`执行命令 pnpm i
```shell
pnpm icnpm install
- 启动项目
-
启动命令看 package.json -> scripts { … }
pnpm dev
访问控制台地址,启动效果如下然后使用开发工具,导入创建好的项目
3.项目结构认识
├── dist/ //代码编译目录
└── src/├── api/ // 接口请求目录├── assets/ // 静态资源目录├── common/ // 通用类库目录├── components/ // 公共组件目录├── router/ // 路由配置目录├── store/ // 状态管理目录├── style/ // 通用样式目录├── utils/ // 工具函数目录├── views/ // 页面组件目录├── App.vue├── main.ts //主ts
├── tests/ // 单元测试目录
├── index.html //默认首页,入口页面
├── jsconfig.json // JavaScript 配置文件
├── vite.config.js // Vite 配置文件
└── package.json //依赖管理
└── vite.config.ts
4.route的使用
4.1.安装route路由
官网:https://router.vuejs.org/zh/installation.html
pnpm install vue-router@4
4.2.创建页面
在src目录下创建views/order/order.vue 以及 views/user/user.vue 页面文件,内容如下
<script setup lang="ts"></script><template><div>11111111111111</div>
</template><style scoped>
</style>
----
4.3.创建router
在src目录下创建router/index.ts文件,内容如下
import { createRouter, createWebHashHistory } from 'vue-router'const router = createRouter({history: createWebHashHistory(),routes: [{path:"/",name:"home",component:()=>import("../views/User.vue")},{path:"/test",name:"test",component:()=>import("../views/Order.vue"),}],})export default router
-
createWebHashHistory: hash 模式下使用的是createWebHashHistoryapi 进行配置 VueRouter 的历史模式。使用 hash 模式下会在浏览器网页路由当中使用哈希字符(#)对 url 进行切割并且匹配哈希字符后的字符进行判断路由匹配与跳转处理。
4.4.在main.js中使用路由
...省略...
//导入路由import { createApp } from 'vue’import router from ‘./router/index’
//创建appconst app = createApp(App);
//使用路由app.use(router)
//挂载appapp.mount(‘#app’)
#### 4.5.在App.vue文件中指定路由出口
```vue
<template><div><!-- 路由出口 ,所有页面都会放到这个里面--><router-view></router-view></div>
</template>
4.6.重启测试
5.vue的核心语法
5.1 简单入门
Vue3
向下兼容Vue2
语法,且Vue3
中的模板中可以没有根标签
<template><div class="person"><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><button @click="changeName">修改名字</button><button @click="changeAge">年龄+1</button><button @click="showTel">点我查看联系方式</button></div>
</template><script lang="ts">export default {name:'App',data() {return {name:'张三',age:18,tel:'13888888888'}},methods:{changeName(){this.name = 'zhang-san'},changeAge(){this.age += 1},showTel(){alert(this.tel)}},}
</script>
5.2 Setup函数
在学习以下知识之前我们先了解一下 options api 和 composition api的区别:vue2中使用的是optionsAPI,来定义一个组件内部的一些属性,如methods、data等等;其缺点往往会在大项目中体现出来,比如一个简单的计数器功能,可能需要在methods内部书写一部分逻辑,在computed内部也书写一部分逻辑,那么问题来了:如果该组件内部有n个这样的小功能,那么此时代码逻辑是十分分散的,并且后期迭代维护面临的问题也是可能修改一个需求需要在不同的属性内部反复查找相关的代码,而compositionAPI的出现就是为了解决这一问题的。
vue3新增的compositionAPI主要就是为了解决API太过于分散的问题,避免一个功能点下的api太过于分散不便于维护,将同一个功能下的api统一放到一个地方,这样一来项目的开发和维护就简便多了。compositionAPI也叫做组合式API,vue3中组合式API的入口就是setup函数;
https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bc0be8211fc54b6c941c036791ba4efe~tplv-k3u1fbpfcp-watermark.image
setup
是Vue3
中一个新的配置项,值是一个函数,它是 Composition API
“表演的舞台”,组件中所用到的:数据、方法、计算属性、监视…等等,均配置在setup
中。
特点如下:
setup
函数返回的对象中的内容,可直接在模板中使用。setup
中访问this
是undefined
。setup
函数会在beforeCreate
之前调用,它是“领先”所有钩子执行的。
5.1.普通写法[不推荐]
通过 export default { setup(){ } } 来定义setup函数 , 这种方式不推荐
<template><div class="person"><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><button @click="changeName">修改名字</button><button @click="changeAge">年龄+1</button><button @click="showTel">点我查看联系方式</button></div>
</template><script lang="ts">export default {name:'Person',setup(){// 数据,原来写在data中(注意:此时的name、age、tel数据都不是响应式数据)let name = '张三'let age = 18let tel = '13888888888'// 方法,原来写在methods中function changeName(){name = 'zhang-san' //注意:此时这么修改name页面是不变化的console.log(name)}function changeAge(){age += 1 //注意:此时这么修改age页面是不变化的console.log(age)}function showTel(){alert(tel)}// 返回一个对象,对象中的内容,模板中可以直接使用return {name,age,tel,changeName,changeAge,showTel}}}
</script>
5.2.语法糖[推荐]
注意:在setup语法中使用 this 会出现unidefined , 在 `setup` 中你应该避免使用 `this`,因为它不会找到组件实例。`setup` 的调用发生在 `data` property、`computed` property 或 `methods` 被解析之前,所以它们无法在 `setup` 中被获取,这也是为了避免setup()和其他选项式API混淆。### 6.数据响应函数
什么是响应式:响应式指的是,把数据绑定当视图,当数据的值发生改变,视图也会跟着重新渲染。
#### 6.1.ref:基础数据响应式函数
VUE3提供了ref函数可以在修改完 数据后自动渲染视图,类似余双向绑定- 在js中通过 import {ref} from "vue"; 来声明 ref函数
- 注意:ref属性在js中要使用.value 取值
```javascript
<template><div class="person"><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><button @click="changeName">修改名字</button><button @click="changeAge">年龄+1</button><button @click="showTel">点我查看联系方式</button></div>
</template><script setup lang="ts" name="Person">import {ref} from 'vue'// name和age是一个RefImpl的实例对象,简称ref对象,它们的value属性是响应式的。let name = ref('张三')let age = ref(18)// tel就是一个普通的字符串,不是响应式的let tel = '13888888888'function changeName(){// JS中操作ref对象时候需要.valuename.value = '李四'console.log(name.value)// 注意:name不是响应式的,name.value是响应式的,所以如下代码并不会引起页面的更新。// name = ref('zhang-san')}function changeAge(){// JS中操作ref对象时候需要.valueage.value += 1 console.log(age.value)}function showTel(){alert(tel)}
</script>
通过ref获取dom对象
<template><input ref="inputRef" :value="refValue"/>
</template><script setup>import {ref} from "vue";//变量名 inputRef 和 dom元素上的ref="inputRef" 一样,就能获取到dom元素let inputRef = ref(null);console.log(inputRef);</script>
6.2.reactive创建:对象类型的响应式数据
对于对象类型可以使用reactive进行响应式函数处理
<template><div class="person"><h2>汽车信息:一台{{ car.brand }}汽车,价值{{ car.price }}万</h2><h2>游戏列表:</h2><ul><li v-for="g in games" :key="g.id">{{ g.name }}</li></ul><h2>测试:{{obj.a.b.c.d}}</h2><button @click="changeCarPrice">修改汽车价格</button><button @click="changeFirstGame">修改第一游戏</button><button @click="test">测试</button></div>
</template><script lang="ts" setup name="Person">
import { reactive } from 'vue'// 数据
let car = reactive({ brand: '奔驰', price: 100 })
let games = reactive([{ id: 'ahsgdyfa01', name: '英雄联盟' },{ id: 'ahsgdyfa02', name: '王者荣耀' },{ id: 'ahsgdyfa03', name: '原神' }
])
let obj = reactive({a:{b:{c:{d:666}}}
})function changeCarPrice() {car.price += 10
}
function changeFirstGame() {games[0].name = '流星蝴蝶剑'
}
function test(){obj.a.b.c.d = 999
}
</script>
注意:其实ref
接收的数据可以是:基本类型、对象类型。若ref
接收的是对象类型,内部其实也是调用了reactive
函数
例如:
<template><div class="person"><h2>汽车信息:一台{{ car.brand }}汽车,价值{{ car.price }}万</h2><h2>游戏列表:</h2><ul><li v-for="g in games" :key="g.id">{{ g.name }}</li></ul><h2>测试:{{obj.a.b.c.d}}</h2><button @click="changeCarPrice">修改汽车价格</button><button @click="changeFirstGame">修改第一游戏</button><button @click="test">测试</button></div>
</template><script lang="ts" setup name="Person">
import { ref } from 'vue'// 数据
let car = ref({ brand: '奔驰', price: 100 })
let games = ref([{ id: 'ahsgdyfa01', name: '英雄联盟' },{ id: 'ahsgdyfa02', name: '王者荣耀' },{ id: 'ahsgdyfa03', name: '原神' }
])
let obj = ref({a:{b:{c:{d:666}}}
})console.log(car)function changeCarPrice() {car.value.price += 10
}
function changeFirstGame() {games.value[0].name = '流星蝴蝶剑'
}
function test(){obj.value.a.b.c.d = 999
}
</script>
对比:
ref
用来定义:基本类型数据、对象类型数据;reactive
用来定义:对象类型数据。ref
创建的变量必须使用.value
reactive
重新分配一个新对象,会失去响应式
7.toRef 和 toRefs【了解】
7.1.toRef解析响应式
将一个响应式对象中的每一个属性,转换为ref
对象,toRefs
与toRef
功能一致,但toRefs
可以批量转换。
<template><div class="person"><h2>姓名:{{person.name}}</h2><h2>年龄:{{person.age}}</h2><h2>性别:{{person.gender}}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年龄</button><button @click="changeGender">修改性别</button></div>
</template><script lang="ts" setup name="Person">import {ref,reactive,toRefs,toRef} from 'vue'// 数据let person = reactive({name:'张三', age:18, gender:'男'})// 通过toRefs将person对象中的n个属性批量取出,且依然保持响应式的能力let {name,gender} = toRefs(person)// 通过toRef将person对象中的gender属性取出,且依然保持响应式的能力let age = toRef(person,'age')// 方法function changeName(){name.value += '~'}function changeAge(){age.value += 1}function changeGender(){gender.value = '女'}
</script>
8.computed计算函数
下面通过计算函数演示2个值相加,需要通过 import {computed} from “vue”; 引入函数
<template><div class="person">姓:<input type="text" v-model="firstName"> <br>名:<input type="text" v-model="lastName"> <br>全名:<span>{{fullName}}</span> <br><button @click="changeFullName">全名改为:li-si</button></div>
</template><script setup lang="ts" name="App">import {ref,computed} from 'vue'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]}})function changeFullName(){fullName.value = 'li-si'}
</script>
9.watch监听器
watch的作用是用来监听某个值的变化从而出发watch回调。watch可以:
-
监听单体数据
-
监听对象
-
监听对象的某个属性
-
监听多个数据
<template><div class="watch"></div><button @click="value++">修改value</button><button @click="persion.age++">修改persion</button> </template> <script setup>
import {reactive, ref, watch} from “vue”;
const value = ref(1111);const persion = reactive({ username:“zs” , age:18})//监听单个值watch(value,()=>{ console.log(value);})//监听对象watch(persion,()=>{ console.log(persion);})
//监听多个,用[]包含多个watch([persion,value],()=>{ console.log(persion,value);});
//监听对象的属性 watch(persion.age,()=>{ console.log(persion);});
watch如果监听对象,可以深度监听(即:对象中又深度嵌套了对象,嵌套的对象值被修改也会被监听到)
### 10.watchEffect监听副作用[了解]
立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数。watchEffect的作用是只要使用到的数据都会被监听到,无需指定具体监听谁,他包含4个功能- 页面加载,watchEffect的回调就会立即执行
- 只要watchEffect 只要里面使用了任何数据,就会触发回到函数
- onCleanup函数可以清除副作用
- 返回一个stopHandler可以停止监听
```vue
<template><div class="watch_effect"></div><button @click="num++">num++</button><button @click="stopHandler">StopWatch</button>
</template><script setup>import {ref, watchEffect} from "vue";const num = ref(1111);//1.页面加载,初始化立即回执行回调
//2.只要watchEffect 只要里面使用了数据,就会触发回到函数
//3.onCleanup函数可以清除副作用
//4.返回一个stopHandler可以停止监听
/**
watchEffect(()=>{console.log(num.value);
})**/const stopHandler = watchEffect(onCleanup=>{console.log(num.value);onCleanup(()=>{console.log("清除副作用");});
})</script>
11.生命周期方法
因为 setup
是围绕 beforeCreate
和 created
生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup
函数中编写。下表包含如何在 setup () 内部调用生命周期钩子:
选项式 API(VUE2) | Hook inside setup |
---|---|
beforeCreate | Not needed* 不需要 |
created | Not needed* 不需要 |
beforeMount | onBeforeMount |
挂载之前 | |
mounted | onMounted |
页面加载完成时执行 | |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
页面销毁时执行 | |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
activated | onActivated |
deactivated | onDeactivated |
下面是代码案例
<script setup>
import {onBeforeMount, onBeforeUnmount, onMounted, onUnmounted} from "vue";console.log("初始化")
onMounted(()=>{console.log("onMounted 挂载中...");
})onBeforeMount(()=>{console.log("onBeforeMount 挂载前....");
})
onUnmounted(()=>{console.log("onUnmounted 当销毁..")
})
onBeforeUnmount(()=>{console.log("onBeforeUnmount 当销毁前..")
})
</script>
12.父子组件[重点]
12.1.父传参数给子
第一步:创建一个父组件页面,引入子组件,并传入参数 parent.vue
<template><H1 class="parent_child">父传子</H1><h2>使用子组件传参</h2><child-vue text="传入静态文本" :num="num" :title="title" :persion="persion"></child-vue>
</template><script setup>//引入子组件取名用驼峰,页面用中划线 <child-vue></child-vue>
import ChildVue from "@/views/parent_child/child.vue";
import {reactive, ref} from "vue";//构建一个响应式对象
const persion = reactive({username:"zs",age:18
})
//定义响应式变量
const title = ref("我是标题");
const num = ref(111);
</script>
第二步:创建一个子组件页面 : child.vue , 通过 defineProps接受父传入的参数
<template><h2>text:{{text}}</h2><h2>num:{{num}}</h2><h2>title:{{title}}</h2><h2>persion:{{persion}}</h2>
</template><script setup>
//通过defineProps来接受父传入过来的参数
const prop = defineProps({text:String,num:Number,title:String,persion:Object
})
//在js中可以读取父传入过来的参数,但是不能修改
console.log("prop.title" , prop.title)</script>
注意:在子组件中没法修改父转件传入的参数
12.2.子传参给父
子组件给父组件传值,需要通过defineEmits在子组件声明事件方法,然后调用该事件方法传值,父组件需要监听该事件方法取值。第一步:在子组件中准备一个按钮,点击按钮就给父组件传值。然后给按钮绑定一个方法。
<h1>给父组件传参</h1>
<button @click="sendData2Parent">给父组件传参</button>------------------------------------------------------------------------------------------//声明事件
const $emits = defineEmits(["data2parent","事件名","事件名"]);const sendData2Parent = ()=>{//调用声明的事件$emits('data2parent',"传给父组件的数据");
}
- const $emits = defineEmits([“data2parent”,“事件2”,“事件3”]); 自定义了三个事件
- $emits(‘data2parent’,“传给父组件的数据”); 调用自定义的事件
第二步:在父组件中绑定该事件,并调用一个函数来接受值
<child-vue @data2parent="getDataFromChild" ...></child-vue>const getDataFromChild = (data)=>{console.log("打印数据",data);
}
-
@data2parent=“getDataFromChild” : data2parent对应了子组件中声明的事件,拿到数据后交给getDataFromChild去处理。
12.3.子暴露
VUE3提供了 defineExpose 来暴露子组件中的数据和方法在父组件中可以拿到子组件中暴露的数据或者方法。第一步在子组件中暴露数据
//子暴露 defineExpose({data:"我是子暴露的数据",sayHi:()=>{return "我是子暴露的方法被调用返回的结果";} })
第二步:在父组件接受子暴露的数据
<child-vueref="childVueRef" ...></child-vue>
//用一个响应式变量,接受子暴露的数据const childVueRef = ref(null);onMounted(()=>{ console.log(“子暴露的数据:”,childVueRef.value.data); console.log(“子暴露的方法:”,childVueRef.value.sayHi());})
- ref="childVueRef" : 接受子暴露,把数据绑定给childVueRef注意:因为在父组件的 setup 函数中,声明周期很早,此时子组件还没加载,所以需要在onMounted去调用子暴露的数据和方法才可以拿到结果。### 13.自定义Hooks函数
**hooks就是用来给我们抽取公共代码的**,hooks函数就是通过 use开头的js参数 。 自定义`hook`的优势:复用代码, 让`setup`中的逻辑更清楚易懂。
代码如下: useSum.ts```js
import {ref,onMounted} from 'vue'export default function(){let sum = ref(0)const increment = ()=>{sum.value += 1}const decrement = ()=>{sum.value -= 1}onMounted(()=>{increment()})//向外部暴露数据return {sum,increment,decrement}
}
useDog.ts
中内容如下
import {reactive,onMounted} from 'vue'
import axios,{AxiosError} from 'axios'export default function(){let dogList = reactive<string[]>([])// 方法async function getDog(){try {// 发请求let {data} = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')// 维护数据dogList.push(data.message)} catch (error) {// 处理错误const err = <AxiosError>errorconsole.log(err.message)}}// 挂载钩子onMounted(()=>{getDog()})//向外部暴露数据return {dogList,getDog}
}
组件中使用:
<template><h2>当前求和为:{{sum}}</h2><button @click="increment">点我+1</button><button @click="decrement">点我-1</button><hr><img v-for="(u,index) in dogList" :key="index" :src="u"> <span v-show="dogList.isLoading">加载中......</span><br><button @click="getDog">再来一只狗</button>
</template><script lang="ts">import {defineComponent} from 'vue'export default defineComponent({name:'App',})
</script><script setup lang="ts">import useSum from './hooks/useSum'import useDog from './hooks/useDog'let {sum,increment,decrement} = useSum()let {dogList,getDog} = useDog()
</script>