一、Vue3.0的介绍
2020年09月18日,vue3.0正式发布,命名为One Piece
Vue3和Vue2对比
和vue2.0相比,vue3.0的变化主要有以下几点
-
组合API和选项式API,开发人员根据自己情况和习惯,可以选择不同写法,组合式更加接近与原生的写法
-
性能提升:Vue3这个框架将Vue全部重构了。新的框架。底层采用TS来进行重构,性能提升能达到100%
-
对TS的支持,Vue3底层默认采用TS进行开发。我们Vue开发过程中,一般也会默认结合TS来使用
-
按需加载
-
新增组件\:Fragment、Teleport、Suspense
-
Vue3目前创建项目采用了新的打包工具,vite工具(xxx)团队他们自己发布的一个打包。目标干掉webpack
优势:
-
更快:vue3重写了虚拟dom。性能提升很多
-
更小:新增了tree shaking技术,你们项目中没有用到的代码。打包会默认去掉。
-
更易于维护:Flow到TypeScript变化,让我们代码实现起来更加规范
官方网址:Vue.js - 渐进式 JavaScript 框架 | Vue.js
二、使用vite工具创建项目
官网:Vite
1、Vite的介绍
Vite是一个 web 开发构建工具,它和以前使用v-cli的作用基本相同。
Vite这个单词是一个法语单词,意思就是轻快的意思
vite的特征介绍
-
Vite主打特点就是轻快冷服务启动。冷服务的意思是,在开发预览中,它是不进行打包的
-
开发中可以实现热更新,也就是说在你开发的时候,只要一保存,结果就会更新
-
按需进行更新编译,不会刷新全部DOM节点。这功能会加快我们的开发流程度。
注意\:vite目前它只支持
Vue3.x
的版本,不支持Vue2.x
版本
2、使用vite创建vue3.0应用
通过npm方式
npm init vite-app <project-name>
cd <project-name>
npm install //安装依赖包
npm run dev //启动项目
通过yarn方式
yarn create vite <project-name>
cd <project-name>
yarn
yarn dev
3、创建一个应用
每个Vue应用都是通过createApp函数创建一个新的应用实例
我们传入 createApp
的对象实际上是一个组件,每个应用都需要一个“根组件”,其他组件将作为其子组件。
应用实例必须在调用了 .mount()
方法后才会渲染出来。该方法接收一个“容器”参数,可以是一个实际的 DOM 元素或是一个 CSS 选择器字符串:
import { createApp } from 'vue'
// 从一个单文件组件中导入根组件
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
4、项目插件和模板设置
开发Vue3,我们可以将Vue2插件关闭了,不然可能影响你们的代码编译
-
禁用Vetur插件
-
Vue3开发,我们需要在vscode中安装插件:
-
配置组件创建的快捷键
新建一个代码片段,名字vue3,打开你们的片段文件,内容拷贝进去
{
"Print to console": {
"prefix": "vue3",
"body": [
"<template>",
" <div></div>",
"</template>",
"",
"<script lang='ts' setup>",
"</script>",
"",
"<style lang='scss' scoped>",
"</style>"
],
"description": "Log output to console"
}
}
5、一个问题
Cannot find module './App.vue' or its corresponding type declarations.ts
6、安装sass
yarn add sass --dev
三、vite常见配置
1、resolve.alias
定义路径别名也是我们常用的一个功能,我们通常会给 src
定义一个路径别名
-
安装path依赖包
yarn add -D @types/node
-
在tsconfig.json中配置如下,确保types中含有node
{
"compilerOptions": {
"types": ["node"]
}
}
-
在vite.config.ts中配置如下
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import {resolve} from 'path'
export default defineConfig({
plugins: [vue()],
resolve:{
alias:{
'@': resolve(__dirname, './src')
}
}
})
-
在项目根目录创建tsconfig.json配置文件
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
}
}
2、build.outdir
指定打包文件的输出目录。默认值为 dist
,当 dist
被占用或公司有统一命名规范时,可进行调整。
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
build: {
outDir: 'build' // 打包文件的输出目录
}
})
3、css.preprocessorOptions
传递给 CSS 预处理器的配置选项,这些配置会传递到预处理器的执行参数中去。例如,在 scss 中定义一个全局变量:
-
在variables.scss文件中定义变量
// src/assets/styles/variables.scss
$injectedColor: orange;
$injectedFontSize: 16px;
-
在vite.config.ts文件中进行如下配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import {resolve} from 'path'
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
additionalData: `@import '/src/assets/styles/variables.scss';` // 引入全局变量文件
}
}
}
})
-
在组件中使用
<style lang='scss' scoped>
.box{
font-size: 18px;
color:$injectedColor
}
</style>
4、server.proxy
反向代理也是我们经常会用到的一个功能,通常我们使用它来进行跨域
解决跨域的解决办法
-
JSONP:只适合GET请求的跨域解决
-
CORS:这种是在后端来实现跨域问题的解决方案
-
Server.Proxy:是在前端的脚手架下安装server插件,在插件中通过proxy来完成跨域配置,将项目打包之后,跨域问题还存在,这种方法只适合在开发阶段使用
-
Nginx中进行跨域配置
1.在vite.config.ts中配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
server:{
proxy:{
'/api': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
2.在组件中使用
let result =await axios.get('api/qcross/home/index.php?action=recommend')//记得加api
console.log(result);
四、组合式API-setup关键字
setup用法有两种,一种是采用setup函数方式、另外一种是采用<script setup>
方式
1、setup函数
-
setup是组合式API的入口
-
组件中所用到的数据、方法等,均要配置在setup中
-
setup函数的两种返回值
-
若返回一个对象,则对象中的属性,方法在模板中均可以直接使用。
-
若返回一个渲染函数:则可以自定义渲染内容(了解即可)
-
关键代码:setup函数的返回值是一个对象
<template>
<h1>个人简介</h1>
<div>姓名:{{nickName}}</div>
<div>年龄:{{age}}</div>
<div>介绍:{{introduce()}}</div>
</template>
<script lang="ts">
export default{
setup() {
let nickName:string='Giles'
let age=38
const introduce:()=>string=()=>`我叫${nickName},今年${age}岁`
return{nickName,age,introduce}
}
}
</script>
-
注意点:
-
尽量不要与vue2混用
-
vue2.x配置中可以访问到setup中的属性、方法
-
当在setup中不能访问到vue2.x配置
-
如果有重名,setup优先
-
-
setup不能是一个async函数,因为返回值不再是return对象,而是Promise,模板看不到return对象中的属性。
-
2、<script setup>
在 setup()
函数中手动暴露大量的状态和方法非常繁琐。幸运的是,我们可以通过使用构建工具来简化该操作。当使用单文件组件(SFC)时,我们可以使用 <script setup>
来大幅度地简化代码。
<template>
<h1>个人简介</h1>
<div>姓名:{{nickName}}</div>
<div>年龄:{{age}}</div>
<div>介绍:{{introduce()}}</div>
</template>
<script setup lang="ts">
let nickName:string='Giles'
let age:number=38;
const introduce:()=>string=()=>`我叫${nickName},今年${age}岁`
</script>
五、组合式API-vue3的响应式
1、ref函数
1.1、引入
直接定义在setup函数中的变量不是一个响应式数据,修改它页面不会自动更新
<template>
<div>
<div>{{count}}</div>
<button @click="increment">+</button>
</div>
</template>
<script>
export default {
setup(){
let count=0
const increment=()=>{
console.log('count',count);
return count++
}
return{count,increment}
}
}
</script>
为了解决这个问题,我们可以使用ref来完成
1.2 、实现步骤
实现步骤
-
导入ref函数:
import {ref} from 'vue'
-
创建一个包含响应式数据的引用对象:
let xx=ref(初始数据)
-
JS中操作数据::
xx.value
-
模板中读取数据:不要value,直接
<div>{{xx}}</div>
关键代码
<template>
<div>
<div>{{count}}</div>
<button @click="increment">+</button>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
setup(){
let count=ref(0)
const increment=()=>{
console.log('count',count.value);
return count.value++
}
return{count,increment}
}
}
</script>
1.3、注意
-
接收的数据可以是:基本数据类型,也可以是对象类型
-
基本类型的数据:响应式依然是靠
Object.defineProperty()
的get与set完成的 -
对象类型的数据:内部使用了
reactive函数
2、reactive函数
-
作用:定义了一个对象类型的响应式数据(基本类型用ref函数)
-
语法
const 代理对象=reactive(被代理对象)
-
reactive定义的响应式数据是深层次的
-
内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据都是响应式的。
关键代码
setup(){
let wife=reactive({name:'Monica'})
const changeInfo=()=>{
wife.name="Marry"
}
return{wife,changeInfo}
}
3、比较vue2和vue3的响应式
3.1、Vue2的响应式
-
对象: 通过 defineProperty 对对象的已有属性值的读取和修改进行劫持(监视/拦截)
-
数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
-
存在问题:对象直接新添加的属性或删除已有属性, 界面不会自动更新;直接通过下标替换元素或更新 length, 界面不会自动更新
3.2、Vue3的响应式
-
通过 Proxy(代理): 拦截对 data 任意属性的任意(13 种)操作, 包括属性值的读写, 属 性的添加, 属性的删除等…
-
通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作
let obj={}
let proxy=new Proxy(obj,{
get(target,key){
console.log('-----get------');
return Reflect.get(target, key)
},
set(target,key,value){
console.log('-----set------');
return Reflect.set(target, key, value)
},
deleteProperty(target,key){
return Reflect.deleteProperty(target,key)
}
})
proxy.name='Giles'
proxy.age=38
proxy.job='teacher'
console.log(proxy);
delete proxy.job
console.log(proxy);
六、组合式API-计算属性
1、回顾vue2中的computed
在vue2中计算属性的定义有两种形式
-
基本使用
<template>
<div>
firstName:<input type="text" v-model="firstName"><br>
middleName:<input type="text" v-model="middleName"><br>
lastName:<input type="text" v-model="lastName"><br>
fullName:{{fullName}}
</div>
</template>
<script>
export default {
data(){
return{
firstName:'zhai',
middleName:'ji',
lastName:'zhe'
}
},
computed:{
fullName:function(){
return this.firstName+" "+this.middleName+" "+this.lastName
}
}
}
</script>
-
计算属性的setter和getter
<template>
<div>
firstName:<input type="text" v-model="firstName"><br>
middleName:<input type="text" v-model="middleName"><br>
lastName:<input type="text" v-model="lastName"><br>
fullName:<input type="text" v-model="fullName"><br>
result:{{fullName}}
</div>
</template>
<script>
export default {
data(){
return{
firstName:'zhai',
middleName:'ji',
lastName:'zhe'
}
},
computed:{
fullName:{
set(newval){
this.firstName=newval.split(" ")[0]
this.middleName=newval.split(" ")[1]
this.lastName=newval.split(" ")[2]
},
get(){
return this.firstName+" "+this.middleName+" "+this.lastName
}
}
}
}
</script>
2、vue3中的computed
2.1、基本使用
<template>
<div>
<div>firstName <input type="text" v-model="person.firstName"></div>
<div>middleName <input type="text" v-model="person.middleName"></div>
<div>lastName <input type="text" v-model="person.lastName"></div>
<div>fullName:{{person.fullName}}</div>
</div>
</template>
<script lang='ts' setup>
import { reactive,computed} from 'vue'
//定义接口
interface Person{
firstName:string,
middleName:string,
lastName:string,
[propName:string]:any
}
const person:Person=reactive({
firstName:'zhai',
middleName:'ji',
lastName:'zhe'
})
person.fullName=computed(()=>person.firstName+" "+person.middleName+" "+person.lastName)
</script>
<style lang='scss' scoped>
</style>
2.2、计算属性的setter和getter
<template>
<div>
<div>firstName <input type="text" v-model="person.firstName"></div>
<div>middleName <input type="text" v-model="person.middleName"></div>
<div>lastName <input type="text" v-model="person.lastName"></div>
<div>fullName:<input type="text" v-model="person.fullName"></div>
<div>fullName:{{person.fullName}}</div>
</div>
</template>
<script lang='ts' setup>
import { reactive, computed } from 'vue'
//定义接口
interface Person {
firstName: string,
middleName: string,
lastName: string,
[propName: string]: any
}
const person: Person = reactive({
firstName: 'zhai',
middleName: 'ji',
lastName: 'zhe'
})
person.fullName = computed({
set(newval:string) {
person.firstName = newval.split(" ")[0]
person.middleName = newval.split(" ")[1]
person.lastName = newval.split(" ")[2]
},
get():string {
return person.firstName + " " + person.middleName + " " + person.lastName
}
})
</script>
<style lang='scss' scoped>
</style>
七、组合式API-watch监控
1、回顾vue2中的wath
在vue2中监视属性的定义有两种形式
-
简写形式
export default {
data() {
return {
num: 0,
};
},
watch: {
num: function (newval, oldval) {
console.log(newval, oldval);
},
},
};
-
标准形式
export default {
data() {
return {
num:0
};
},
watch:{
num:{
handler:function(newval,oldval){
console.log(newval,oldval);
},
deep:true,
immediate:true
}
}
};
2、vue3中的watch的各种情况
-
监听单个ref定义的响应式数据
<script lang='ts' setup>
import { ref, watch } from 'vue'
let age = ref(20)
/*
监听单个ref定义的响应式数据
语法
watch(ref变量,()=>{},{})
*/
watch(age, (newval, oldval) => {
console.log(newval, oldval);
}, { immediate: true })
</script>
-
监听多个ref定义的响应式数据
import {ref,watch} from 'vue'
export default {
setup(){
let name=ref('Giles')
let age=ref("age")
/*
监听多个ref定义的响应式数据
语法
watch([ref变量1,ref变量2],()=>{},{})
*/
watch([name,age],(newval,oldval)=>{
console.log('name或age变化了',newval,oldval);
},{immediate:true})
return{name,age}
}
}
-
监听reactive定义的响应式数据
import {computed, reactive,watch} from 'vue'
export default {
setup(){
/*
监听reactive定义的响应式数据
*/
let person=reactive({
name:'Giles',
age:38
})
let newPerson=computed(()=>{
return JSON.parse(JSON.stringify(person))
})
watch(newPerson,(newval,oldval)=>{
console.log('person变化了',newval,oldval);
},{immediate:true})
return {person}
}
}
如上要注意两点:1)如果要获取oldvalue,必须要定义计算属性;2)强制开启了深度监听(deep无效)
-
监听reactive定义的响应式数据中某个属性
export default {
setup(){
/*
监听reactive定义的响应式数据中某个属性
语法
watch(()=>对象.属性名,()=>{},{})
*/
let person=reactive({
name:'Giles',
age:38
})
watch(()=>person.name,(newval,oldval)=>{
console.log('person.name变化了',newval,oldval);
},{immediate:true})
return {person}
}
}
注意:如果要监听的是对象中某个属性,watch的第一个参数必须是一个函数,否则将监听不到
3、watchEffect
这个api是vue3新增的一个特性,使用这个api你可以上对多个属性的监控
watchEffect(()=>{
// 无需指定要监控哪个变量。只要你在这个watcheffect模块中使用了哪个变量,都会监控
person.name
console.log('watchEffect执行了');
})
只要我们watchEffect这个模块使用到的变量发生变化,回调函数就会立即执行。
首次进来也会执行一次。
如果里面没有使用到某个属性,这个属性发生变化,不会检测。
区别:
watch在监控数据的时候,我们必须要指定监控的内容。watchEffect可以监控用到所有变量。
语法层面,watchEffect比watch会更加简洁,watchEffect页面加载的时候默认执行一次,watch默认不会再页面第一次加载的时候就执行,自己配置立即执行属性
watch可以监控得到,修改之前和修改之后的值,watchEffect无法获取这个值
八、组合式API-组件生命周期
Vue3也提供生命周期函数,
组合式api更多帮我们提供api来开发代码。比如setup、生命周期
响应式api,对于页面数据的操作,实现一些动态变化
基于语法
import {onMounted} from "vue"
onMounted(()=>{
console.log("节点挂载完毕后执行")
})
vue2和vue3生命周期对比
选项式 API | Hook inside setup |
---|---|
beforeCreate | Not needed |
created | Not needed |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestory | onBeforeUnmount |
Destoryed | onUnmounted |
你们以后在使用过程中按照以前的函数+On的方式来使用
九、组合式API-组件通讯
-
definedProps可以用于接受组件外部的数据。在TS版本中,我们是通过泛型来获取外部的值
-
definedEmits这个函数可以定义组件访问自定义事件函数,emit对象触发函数,数据回传给父组件
父组件
<template>
<div class="parentBox">
<h2>{{fromCount}}</h2>
<Child :msg="msg" @changeCount="changeCount"></Child>
</div>
</template>
<script lang='ts' setup>
import { reactive,ref} from 'vue'
import Child from './components/Child.vue'
let msg=ref<string>('计数器')
let fromCount=ref<number>(0)
const changeCount=(val:number)=>{
fromCount.value=val
}
</script>
<style lang='scss' scoped>
.parentBox{
position: relative;
width: 500px;
height: 500px;
background-color:orange;
}
</style>
子组件
<template>
<div class="childBox">
<h2>{{msg}}</h2>
<button @click="increment">+</button>
</div>
</template>
<script lang='ts' setup>
import { reactive,ref,defineProps,defineEmits} from 'vue'
defineProps<{msg:string}>()
const emit=defineEmits<{(e:'changeCount',val:number):void}>()
let count=ref<number>(0)
const increment=()=>{
count.value++
emit('changeCount',count.value)
}
</script>
<style lang='scss' scoped>
.childBox{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
width: 300px;
height: 300px;
background-color: red;
}
</style>
十、组合式API-其他
1、toRef和toRefs
1.1、引入
观察如下代码,你会发现在template中调用person对象中的属性,都需要使用person.属性名
,这样在template中操作太麻烦了
<template>
<h1>
<h2>个人信息</h2>
<div>姓名:{{person.name}}</div>
<div>年龄:{{person.age}}</div>
<div>妻子:{{person.wife.name}}</div>
<button @click="updateInfo">修改信息</button>
</h1>
</template>
<script>
import {reactive} from 'vue'
export default {
setup(){
const person=reactive({
name:'Giles',
age:38,
wife:{
name:'Monica'
}
})
const updateInfo=()=>{
person.name="zhaijizhe"
person.age=39
person.wife.name="Marry"
}
return {
person,
updateInfo
}
}
}
</script>
我们可以如下改造
<template>
<h1>
<h2>个人信息</h2>
<div>姓名:{{name}}</div>
<div>年龄:{{age}}</div>
<div>妻子:{{wifeName}}</div>
<button @click="updateInfo">修改信息</button>
</h1>
</template>
<script>
import {reactive} from 'vue'
export default {
setup(){
const person=reactive({
name:'Giles',
age:38,
wife:{
name:'Monica'
}
})
const updateInfo=()=>{
person.name="zhaijizhe"
person.age=39
person.wife.name="Marry"
}
return {
name:person.name,
age:person.age,
wifeName:person.wife.name,
updateInfo
}
}
}
</script>
如上操作虽然可以渲染,但是点击更改按钮,页面数据不会发生变化,如果要改变可以使用toRef和toRefs来完成
1.2、toRef
toRef:可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
<template>
<h1>
<h2>个人信息</h2>
<div>姓名:{{name}}</div>
<div>年龄:{{age}}</div>
<div>妻子:{{wifeName}}</div>
<button @click="updateInfo">修改信息</button>
</h1>
</template>
<script>
import {reactive,toRef} from 'vue'
export default {
setup(){
const person=reactive({
name:'Giles',
age:38,
wife:{
name:'Monica'
}
})
const updateInfo=()=>{
person.name="zhaijizhe"
person.age=39
person.wife.name="Marry"
}
return {
person,
name:toRef(person,'name'),
age:toRef(person,'age'),
wifeName:toRef(person.wife,'name'),
updateInfo
}
}
}
</script>
1.3、toRefs
toRefs: 将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref。
<template>
<h1>
<h2>个人信息</h2>
<div>姓名:{{name}}</div>
<div>年龄:{{age}}</div>
<div>妻子:{{wife.name}}</div>
<button @click="updateInfo">修改信息</button>
</h1>
</template>
<script>
import {reactive,toRefs} from 'vue'
export default {
setup(){
const person=reactive({
name:'Giles',
age:38,
wife:{
name:'Monica'
}
})
const updateInfo=()=>{
person.name="zhaijizhe"
person.age=39
person.wife.name="Marry"
}
return {...toRefs(person),updateInfo}
}
}
</script>
2、Provider和inject
通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。
对于这种情况,我们可以使用一对 provide
和 inject
。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide
选项来提供数据,子组件有一个 inject
选项来开始使用这些数据。
例如,我们有这样的层次结构:
App
└─ Child
├─Grandson
App.vue的关键代码
<template>
<div class="app">
<h2>我是App组件(根组件)---{{info.msg}}</h2>
<Child></Child>
</div>
</template>
<script>
import Child from './components/Child.vue'
import {reactive,provide} from 'vue'
export default {
name: 'App',
components:{Child},
setup(){
const info=reactive({
msg:'HelloGiles'
})
provide("info-key",info)
return {info}
}
}
</script>
<style>
.app{
background: pink;
padding: 10px;
}
</style>
Child.vue关键代码
<template>
<div class="child">
<h2>我是Child组件(子组件)</h2>
<Grandson></Grandson>
</div>
</template>
<script>
import Grandson from './Grandson.vue'
export default {
name: 'Child',
components:{Grandson},
setup(){
}
}
</script>
<style>
.child{
background: springgreen;
padding: 10px;
}
</style>
Grandson.vue关键代码
<template>
<div class="grandson">
<h2>我是Grandson组件(孙组件)-----{{info.msg}}</h2>
</div>
</template>
<script>
import {inject} from 'vue'
export default {
name: 'Child',
setup(){
let info=inject("info-key")
return{info}
}
}
</script>
<style>
.grandson{
background: skyblue;
padding: 10px;
}
</style>
3、传送门Teleport
传送门组件提供一种简洁的方法,可以指定它里面内容的父元素
关键代码\:Dialog.vue
<template>
<button @click="isShow=true">打开模态框</button>
<teleport to="body">
<div v-if="isShow" class="dialog">
<h1>我是弹框</h1>
<div>内容</div>
<div>内容</div>
<div>内容</div>
<div>内容</div>
<div>内容</div>
<div>内容</div>
<button @click="isShow=false">关闭</button>
</div>
</teleport>
</template>
<script>
import {ref} from 'vue'
export default {
setup(){
let isShow=ref(false)
return {isShow}
}
}
</script>
<style>
.dialog{
position:absolute;
top:50%;
left: 50%;
transform: translate(-50%,-50%);
width: 300px;
height: 300px;
background-color: orange;
text-align: center;
}
</style>