2020年9月18日,Vue.js
发布版3.0
版本,代号:One Piece
(n
打包大小减少41%
。
初次渲染快55%
, 更新渲染快133%
。
内存减少54%
。
使用Proxy
代替defineProperty
实现响应式。
重写虚拟DOM
的实现和Tree-Shaking
。
Vue3
可以更好的支持TypeScript
。
Composition API
(组合API
):
setup
ref
与reactive
computed
与watch
......
新的内置组件:
Fragment
Teleport
Suspense
......
其他改变:
新的生命周期钩子
data
选项应始终被声明为一个函数
移除keyCode
支持作为v-on
的修饰符
......
Vue2
的API
设计是Options
(配置)风格的。
Vue3
的API
设计是Composition
(组合)风格的。
Options
类型的 API
,数据、方法、计算属性等,是分散在:data
、methods
、computed
中的,若想新增或者修改一个需求,就需要分别修改:data
、methods
、computed
,不便于维护和复用。
可以用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。
setup
是Vue3
中一个新的配置项,值是一个函数,它是 Composition API
“表演的舞台”,组件中所用到的:数据、方法、计算属性、监视......等等,均配置在setup
中。
特点如下:
setup
函数返回的对象中的内容,可直接在模板中使用。
setup
中访问this
是undefined
。
setup
函数会在beforeCreate
之前调用,它是“领先”所有钩子执行的。
若返回一个对象:则对象中的:属性、方法等,在模板中均可以直接使用(重点关注)。
若返回一个函数:则可以自定义渲染内容,代码如下:
setup(){
return ()=> '你好啊!'
}
Vue2
的配置(data
、methos
......)中可以访问到 setup
中的属性、方法。
但在setup
中不能访问到Vue2
的配置(data
、methos
......)。
如果与Vue2
冲突,则setup
优先。
setup
函数有一个语法糖,这个语法糖,可以让我们把setup
独立出去,代码如下:
<!-- 下面的写法是setup语法糖 -->
<script setup lang="ts">
console.log(this) //undefined
// 数据(注意:此时的name、age、tel都不是响应式数据)
let name = '张三'
let age = 18
let tel = '13888888888'
// 方法
function changName(){
name = '李四'//注意:此时这么修改name页面是不变化的
}
function changAge(){
console.log(age)
age += 1 //注意:此时这么修改age页面是不变化的
}
function showTel(){
alert(tel)
}
</script>
作用:定义响应式变量。
语法:let xxx = ref(初始值)
。
返回值:一个RefImpl
的实例对象,简称ref对象
或ref
,ref
对象的value
属性是响应式的。
注意点:
JS
中操作数据需要:xxx.value
,但模板中不需要.value
,直接使用即可。
对于let name = ref('张三')
来说,name
不是响应式的,name.value
是响应式的。
<script setup>
import { ref,reactive } from 'vue'
let n = ref(10); //相当于reactive({value:10});
//在JS中.value,在template省略.value
const o1 = ref({id:1})
const lists = ref([
{id:1,title:'新闻1',content:'1111新闻 新闻 新闻 新闻 新闻'},
{id:2,title:'新闻2',content:'2222新闻 新闻 新闻 新闻 新闻'},
{id:3,title:'新闻3',content:'3333新闻 新闻 新闻 新闻 新闻'},
{id:4,title:'新闻4',content:'4444新闻 新闻 新闻 新闻 新闻'},
{id:5,title:'5555',content:'55555555555'},
{id:6,title:'6666',content:'666666666666'}
]);
// function change(){}
const change = ()=>{
// n.value++;
// console.log(n)
o1.value.id+=1;
// o2.id+=1;
// console.log(o2.id)
lists.value.push({id:lists.value.length+1,title:'6666',content:'66666'})
}
//ref的另一种使用:DOM引用
let myInput = ref();
const action = ()=>{
myInput.value.focus();
myInput.value.style.color='#f60';
console.log(myInput)
}
</script>
作用:定义一个响应式对象(基本类型不要用它,要用ref
,否则报错)
语法:let 响应式对象= reactive(源对象)
。
返回值:一个Proxy
的实例对象,简称:响应式对象。
注意点:reactive
定义的响应式数据是“深层次”的。
<script setup>
import { ref,reactive } from 'vue'
let n = reactive(10); //添加常量出现警告,值不会有响应式特性
//reactive()函数接收一个普通对象,返回一个响应式的数据对象
// const o1 = reactive({id:1});
// const o2 = {id:2}; //非响应式数据
// const lists = reactive([
// {id:1,title:'新闻1',content:'1111新闻 新闻 新闻 新闻 新闻'},
// {id:2,title:'新闻2',content:'2222新闻 新闻 新闻 新闻 新闻'},
// {id:3,title:'新闻3',content:'3333新闻 新闻 新闻 新闻 新闻'},
// {id:4,title:'新闻4',content:'4444新闻 新闻 新闻 新闻 新闻'},
// {id:5,title:'5555',content:'55555555555'},
// {id:6,title:'6666',content:'666666666666'}
// ]);
// function change(){}
const change = ()=>{
n++;
console.log(n)
// o1.id+=1;
// o2.id+=1;
// console.log(o2.id)
// lists.push({id:lists.length+1,title:'6666',content:'66666'})
}
</script>
注意点:reactive定义的响应式数据,如果是从后端请求回来的数据,我们不能直接进行赋值,这样页面被没有进行渲染.
<script setup>
import { ref,reactive } from 'vue'
import Data from './data' //引入外部数据
let n1 = 10;
let n2 = ref(10);
//let n3 = reactive(10);
let o1 = ref([]);
let o2 = reactive([]);
let o3 = reactive({arr:[]});
const change=()=>{
// console.log(n1);
// console.log(n2);
// console.log(n3);
// console.log(o1);
// console.log(o2);
//将data数据赋值给o1,o2
//o1.value = Data;
不用这种方式,避免出现全新的对象赋值等问题
//o2 = Data; //这种方式不行,将原始对象替换了o2对象
//解决方式一
o2.push(...Data);
//另一种解决方式
o3.arr = Data;
}
</script>
其实ref
接收的数据可以是:基本类型、对象类型。
若ref
接收的是对象类型,内部其实也是调用了reactive
函数。
宏观角度看:
ref
用来定义:基本类型数据、对象类型数据;
reactive
用来定义:对象类型数据。
区别:
ref
创建的变量必须使用.value
(可以使用volar
插件自动添加.value
)。
2.reactive
重新分配一个新对象,会失去响应式(可以使用Object.assign
去整体替换)。
使用原则:
若需要一个基本类型的响应式数据,必须使用
ref
。若需要一个响应式对象,层级不深,
ref
、reactive
都可以。若需要一个响应式对象,且层级较深,推荐使用
reactive
。
作用:将一个响应式对象中的每一个属性,转换为ref
对象。
备注:toRefs
与toRef
功能一致,但toRefs
可以批量转换。
语法如下:
<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>
作用:根据已有数据计算出新数据(和Vue2
中的computed
作用一致)。
<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>
作用:监视数据的变化(和Vue2
中的watch
作用一致)
特点:Vue3
中的watch
只能监视以下四种数据:
ref
定义的数据。
reactive
定义的数据。函数返回一个值(
getter
函数)。一个包含上述内容的数组。
我们在Vue3
中使用watch
的时候,通常会遇到以下几种情况:
监视ref
定义的【基本类型】数据:直接写数据名即可,监视的是其value
值的改变。
<template>
?<div class="person">
? ?<h1>情况一:监视【ref】定义的【基本类型】数据</h1>
? ?<h2>当前求和为:{{sum}}</h2>
? ?<button @click="changeSum">点我sum+1</button>
?</div>
</template>
?
<script lang="ts" setup name="Person">
?import {ref,watch} from 'vue'
?// 数据
?let sum = ref(0)
?// 方法
?function changeSum(){
? ?sum.value += 1
}
?// 监视,情况一:监视【ref】定义的【基本类型】数据
?const stopWatch = watch(sum,(newValue,oldValue)=>{
? ?console.log('sum变化了',newValue,oldValue)
? ?if(newValue >= 10){
? ? ?stopWatch()
? }
})
</script>
监视ref
定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。
注意:
若修改的是
ref
定义的对象中的属性,newValue
和oldValue
都是新值,因为它们是同一个对象。若修改整个
ref
定义的对象,newValue
是新值,oldValue
是旧值,因为不是同一个对象了。
<template>
?<div class="person">
? ?<h1>情况二:监视【ref】定义的【对象类型】数据</h1>
? ?<h2>姓名:{{ person.name }}</h2>
? ?<h2>年龄:{{ person.age }}</h2>
? ?<button @click="changeName">修改名字</button>
? ?<button @click="changeAge">修改年龄</button>
? ?<button @click="changePerson">修改整个人</button>
?</div>
</template>
?
<script lang="ts" setup name="Person">
?import {ref,watch} from 'vue'
?// 数据
?let person = ref({
? ?name:'张三',
? ?age:18
})
?// 方法
?function changeName(){
? ?person.value.name += '~'
}
?function changeAge(){
? ?person.value.age += 1
}
?function changePerson(){
? ?person.value = {name:'李四',age:90}
}
?/*
? 监视,情况一:监视【ref】定义的【对象类型】数据,监视的是对象的地址值,若想监视对象内部属性的变化,需要手动开启深度监视
? watch的第一个参数是:被监视的数据
? watch的第二个参数是:监视的回调
? watch的第三个参数是:配置对象(deep、immediate等等.....)
*/
?watch(person,(newValue,oldValue)=>{
? ?console.log('person变化了',newValue,oldValue)
},{deep:true})
?
</script>
监视reactive
定义的【对象类型】数据,且默认开启了深度监视。
<template>
?<div class="person">
? ?<h1>情况三:监视【reactive】定义的【对象类型】数据</h1>
? ?<h2>姓名:{{ person.name }}</h2>
? ?<h2>年龄:{{ person.age }}</h2>
? ?<button @click="changeName">修改名字</button>
? ?<button @click="changeAge">修改年龄</button>
? ?<button @click="changePerson">修改整个人</button>
? ?<hr>
? ?<h2>测试:{{obj.a.b.c}}</h2>
? ?<button @click="test">修改obj.a.b.c</button>
?</div>
</template>
?
<script lang="ts" setup name="Person">
?import {reactive,watch} from 'vue'
?// 数据
?let person = reactive({
? ?name:'张三',
? ?age:18
})
?let obj = reactive({
? ?a:{
? ? ?b:{
? ? ? ?c:666
? ? }
? }
})
?// 方法
?function changeName(){
? ?person.name += '~'
}
?function changeAge(){
? ?person.age += 1
}
?function changePerson(){
? ?Object.assign(person,{name:'李四',age:80})
}
?function test(){
? ?obj.a.b.c = 888
}
?
?// 监视,情况三:监视【reactive】定义的【对象类型】数据,且默认是开启深度监视的
?watch(person,(newValue,oldValue)=>{
? ?console.log('person变化了',newValue,oldValue)
})
?watch(obj,(newValue,oldValue)=>{
? ?console.log('Obj变化了',newValue,oldValue)
})
</script>
监视ref
或reactive
定义的【对象类型】数据中的某个属性,注意点如下:
若该属性值不是【对象类型】,需要写成函数形式。
若该属性值是依然是【对象类型】,可直接编,也可写成函数,建议写成函数。
结论:监视的要是对象里的属性,那么最好写函数式,注意点:若是对象监视的是地址值,需要关注对象内部,需要手动开启深度监视。
<template>
?<div class="person">
? ?<h1>情况四:监视【ref】或【reactive】定义的【对象类型】数据中的某个属性</h1>
? ?<h2>姓名:{{ person.name }}</h2>
? ?<h2>年龄:{{ person.age }}</h2>
? ?<h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2>
? ?<button @click="changeName">修改名字</button>
? ?<button @click="changeAge">修改年龄</button>
? ?<button @click="changeC1">修改第一台车</button>
? ?<button @click="changeC2">修改第二台车</button>
? ?<button @click="changeCar">修改整个车</button>
?</div>
</template>
?
<script lang="ts" setup name="Person">
?import {reactive,watch} from 'vue'
?
?// 数据
?let person = reactive({
? ?name:'张三',
? ?age:18,
? ?car:{
? ? ?c1:'奔驰',
? ? ?c2:'宝马'
? }
})
?// 方法
?function changeName(){
? ?person.name += '~'
}
?function changeAge(){
? ?person.age += 1
}
?function changeC1(){
? ?person.car.c1 = '奥迪'
}
?function changeC2(){
? ?person.car.c2 = '大众'
}
?function changeCar(){
? ?person.car = {c1:'雅迪',c2:'爱玛'}
}
?
?// 监视,情况四:监视响应式对象中的某个属性,且该属性是基本类型的,要写成函数式
?/* watch(()=> person.name,(newValue,oldValue)=>{
? console.log('person.name变化了',newValue,oldValue)
}) */
?
?// 监视,情况四:监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,更推荐写函数
?watch(()=>person.car,(newValue,oldValue)=>{
? ?console.log('person.car变化了',newValue,oldValue)
},{deep:true})
</script>
监视上述的多个数据
<template>
?<div class="person">
? ?<h1>情况五:监视上述的多个数据</h1>
? ?<h2>姓名:{{ person.name }}</h2>
? ?<h2>年龄:{{ person.age }}</h2>
? ?<h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2>
? ?<button @click="changeName">修改名字</button>
? ?<button @click="changeAge">修改年龄</button>
? ?<button @click="changeC1">修改第一台车</button>
? ?<button @click="changeC2">修改第二台车</button>
? ?<button @click="changeCar">修改整个车</button>
?</div>
</template>
?
<script lang="ts" setup name="Person">
?import {reactive,watch} from 'vue'
?
?// 数据
?let person = reactive({
? ?name:'张三',
? ?age:18,
? ?car:{
? ? ?c1:'奔驰',
? ? ?c2:'宝马'
? }
})
?// 方法
?function changeName(){
? ?person.name += '~'
}
?function changeAge(){
? ?person.age += 1
}
?function changeC1(){
? ?person.car.c1 = '奥迪'
}
?function changeC2(){
? ?person.car.c2 = '大众'
}
?function changeCar(){
? ?person.car = {c1:'雅迪',c2:'爱玛'}
}
?
?// 监视,情况五:监视上述的多个数据
?watch([()=>person.name,person.car],(newValue,oldValue)=>{
? ?console.log('person.car变化了',newValue,oldValue)
},{deep:true})
?
</script>
官网:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数。
watch
对比watchEffect
都能监听响应式数据的变化,不同的是监听数据变化的方式不同
watch
:要明确指出监视的数据
watchEffect
:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)。
示例代码:
<template>
<div class="person">
<h1>需求:水温达到50℃,或水位达到20cm,则联系服务器</h1>
<h2 id="demo">水温:{{temp}}</h2>
<h2>水位:{{height}}</h2>
<button @click="changePrice">水温+1</button>
<button @click="changeSum">水位+10</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref,watch,watchEffect} from 'vue'
// 数据
let temp = ref(0)
let height = ref(0)
// 方法
function changePrice(){
temp.value += 10
}
function changeSum(){
height.value += 1
}
// 用watch实现,需要明确的指出要监视:temp、height
watch([temp,height],(value)=>{
// 从value中获取最新的temp值、height值
const [newTemp,newHeight] = value
// 室温达到50℃,或水位达到20cm,立刻联系服务器
if(newTemp >= 50 || newHeight >= 20){
console.log('联系服务器')
}
})
// 用watchEffect实现,不用
const stopWtach = watchEffect(()=>{
// 室温达到50℃,或水位达到20cm,立刻联系服务器
if(temp.value >= 50 || height.value >= 20){
console.log(document.getElementById('demo')?.innerText)
console.log('联系服务器')
}
// 水温达到100,或水位达到50,取消监视
if(temp.value === 100 || height.value === 50){
console.log('清理了')
stopWtach()
}
})
</script>
作用:用于注册模板引用。
用在普通
DOM
标签上,获取的是DOM
节点。用在组件标签上,获取的是组件实例对象。
<template>
<div class="person">
<ul>
<li v-for="item in list" :key="item.id">
{{item.name}}--{{item.age}}
</li>
</ul>
</div>
</template>
<script lang="ts" setup name="Person">
import {defineProps} from 'vue'
import {type PersonInter} from '@/types'
// 第一种写法:仅接收
// const props = defineProps(['list'])
// 第二种写法:接收+限制类型
// defineProps<{list:Persons}>()
// 第三种写法:接收+限制类型+指定默认值+限制必要性
let props = withDefaults(defineProps<{list?:Persons}>(),{
list:()=>[{id:'asdasg01',name:'小猪佩奇',age:18}]
})
console.log(props)
</script>
概念:Vue
组件实例在创建时要经历一系列的初始化步骤,在此过程中Vue
会在合适的时机,调用特定的函数,从而让开发者有机会在特定阶段运行自己的代码,这些特定的函数统称为:生命周期钩子
规律:
生命周期整体分为四个阶段,分别是:创建、挂载、更新、销毁,每个阶段都有两个钩子,一前一后。
Vue2
的生命周期
创建阶段:
beforeCreate
、created
挂载阶段:
beforeMount
、mounted
更新阶段:
beforeUpdate
、updated
销毁阶段:
beforeDestroy
、destroyed
Vue3
的生命周期
创建阶段:
setup
挂载阶段:
onBeforeMount
、onMounted
更新阶段:
onBeforeUpdate
、onUpdated
卸载阶段:
onBeforeUnmount
、onUnmounted
常用的钩子:onMounted
(挂载完毕)、onUpdated
(更新完毕)、onBeforeUnmount
(卸载之前)
什么是hook
?—— 本质是一个函数,把setup
函数中使用的Composition API
进行了封装,类似于vue2.x
中的mixin
。
自定义hook
的优势:复用代码, 让setup
中的逻辑更清楚易懂。
不断持续更新......