前言
说明:本系列文章会持续更新Vue3的学习过程和遇到的难点。
前提:要求对Vue2有一定基础的了解或者使用,否则会有点费劲哦~
一、初识Vue3
看到这篇文章的相信大家对Vue有一定基础的了解,但是更多停留在Vue2的使用上,那么Vue2和Vue3到底有哪些区别呢?
vue的认识
vue是一个渐进式框架,它有两大核心分别是:声明式渲染和响应式
#声明式渲染
理解声明式渲染,来结合命令式来区分
举个例子: <div id="age"></div>
这里有个标签,里面需要填入20,两者不同的操作
**命令式**
这里以jq为例
let box = $('#age');
box.text('20');
需要通过dom操作像是命令一样去改变他的值
**声明式**
<div id="age">{{age}}</div>
<script>
export default{
data{
return{
age:20,
}
}
}
</script>
而声明式只需要告诉他结果是什么,不需要进行一些指令去让他改变
#响应式
以上面的例子来说, 假设点击一个按钮要把这个年龄改成30
那么只需要在点击事件中,将age这个值改成30,那么html中也有相应的渲染新的值
如果要详细了解响应式原理的话可以点击下面的网址学习
二、Vue3和Vue2的区别
Vue3最明显的区别是相对Vue2新增了一个组合式API,也就是写法上的区别,这应该是使用Vue最关心的问题了。什么是组合式API下面用例子来详细说明
API的区别
Vue2使用的API风格叫做选项式API
Vue3则可以使用选项式API和一个新增的组合式API
先来具体看看,不同API的变量、函数、生命周期的区别
**选项式API**
<script>
export default {
// data() 返回的属性将会成为响应式的状态
// 并且暴露在 `this` 上
data() {
return {
count: 0
}
},
// methods 是一些用来更改状态与触发更新的函数
// 它们可以在模板中作为事件监听器绑定
methods: {
increment() {
this.count++
}
},
// 生命周期钩子会在组件生命周期的各个不同阶段被调用
// 例如这个函数就会在组件挂载完成后被调用
mounted() {
console.log(`The initial count is ${this.count}.`)
}
}
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
**组合式API**
<script setup>
import { ref, onMounted } from 'vue'
// 响应式状态
const count = ref(0)
// 用来修改状态、触发更新的函数
function increment() {
count.value++
}
// 生命周期钩子
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
组合式API会更接近原生js的写法,这样简单的例子没法看出两种API的优势, 当一个vue文件相对较大的时候组合式API就会相对更有优势,代码会更加清晰,方便查找。
三、进入实战
了解了Vue3的基本变化之后, 我们直接创建一个Vue3项目来实践一下,看看到底有什么不同
//首先定义一个count变量, 然后点击按钮修改变量count的值
<template>
<div>Count is: {{ count }}</div>
<hr>
<button @click="change">修改</button>
</template>
**组合式API**
//视图没有改变
<script setup>
let count = 0
const change = ()=>{
count = 1
console.log('count:'+count);
}
</script>
**选项式API**
//视图改变
<script>
export default{
data(){
return{
count:0
}
},
methods:{
change(){
this.count = 1
console.log('count:'+this.count);
}
}
}
</script>
上面两种不同的写法会发现选项式API可以实现效果,而组合式API试图上没有发生变化,主要原因是,组合式API中这样定义的变量不是响应式的,所以就算变量值改变了视图也没有发生改变,这不符合我们的要求,所以我们需要将变量定义成响应式即可满足需求。
Vue3的响应式变量
需求:定义的变量满足响应式
ref
- 定义ref
<template>
<div>
<div>Count is: {{ count }}</div>
<hr>
<button @click="change">修改</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0)
const change = ()=>{
console.log(count);
}
</script>
首先引入ref,定义一个变量赋值ref()里面可以放各种类型的变量,可以把变量打印出来看一看。
- 修改ref
那么修改变量的值应该怎么做呢?是直接count = 1 这样赋值吗?来试一试
<script setup>
import { ref } from 'vue';
const count = ref(0)
const change = ()=>{
count = 1
console.log(count);
}
</script>
会发现打印出来的count已经被改成1了, 但是直接赋值的方式会把原来变量直接赋值成1,所以他就又变成非响应式了。
<script setup>
import { ref } from 'vue';
const count = ref(0)
const change = ()=>{
count.value = 1
console.log(count);
}
</script>
修改ref的变量, 使用.value的形式来修改。
- isRef
isRef用来判断一个变量是否是ref变量,使用比较少,具体场景再做分析
<script setup>
import { ref,isRef } from 'vue';
let count = ref(0)
let count2 = 2
const change = ()=>{
console.log(isRef(count)); //true
console.log(isRef(count2)); //false
}
</script>
- shallowRef
shallowRef和ref的区别是一个是浅拷贝一个是深拷贝
上面的例子没法看出区别,来举个新的例子:
<script setup>
import { ref,isRef,shallowRef } from 'vue';
// ref
let count = ref({name:"orange"})
const change = ()=>{
count.value.name = "orange2"
console.log(count); //视图改变
}
// shallowRef
let count = shallowRef({name:"orange"})
const change = ()=>{
count.value.name = "orange2"
console.log(count); //视图未改变
}
</script>
定义一个对象变量,来修改对象中的属性,会发现控制台中的name已经修改,但是视图没有发生变化, 当修改对象中属性的时候,shallowRef就无法响应式的改变属性中的值。
想要修改shallowRef中对象的属性怎么办呢?
<script setup>
import { ref,isRef,shallowRef } from 'vue';
// 1 直接重新赋值对象
let count = shallowRef({name:"orange"})
const change = ()=>{
count.value = {name:"orange2"}
console.log(count); //视图改变
}
// 2 使用ref渲染的同时使用shallowRef,会被影响
<template>
<div>
<div>ref is: {{ countRef }}</div>
<hr>
<div>shallowRef is: {{ count }}</div>
<hr>
<button @click="change">修改</button>
</div>
</template>
<script setup lang="ts">
import { ref,isRef,shallowRef } from 'vue';
let countRef = ref({ name:"blue" })
let count = shallowRef({ name:"orange" })
const change = () => {
countRef.value.name = "green"
count.value.name = "orange2"
console.log(count); //视图改变
}
</script>
直接修改value整个对象重新赋值是可以的, 因为浅拷贝只能到达value这一级,再往里就监听不到了, 所以可以直接把整个对象重新赋值,因为ref会影响shallowRef的渲染所以ref和shallowRef不能一块写。
- ref操作dom
<template>
<div>
<div ref="dom">dom is: dom</div>
<hr>
<button @click="change">修改</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
// const dom = ref()
const dom = ref<HTMLDivElement>()
const change = () => {
//console.log(dom.value.innerText);
console.log(dom.value?.innerText);
}
</script>
- 使用ref操作dom时,在标签中添加ref="xxx",这里的ref和js中定义的变量名要一样
- 获取dom元素 => const dom = ref()
- 这样写innerText的时候是没有提示的, 因为这时候ref()这边的类型推断是any类型, 如果要提示可以加上泛型约束 => const dom = ref<HTMLDivElement>()
reactive
- 定义reactive
<script setup lang="ts">
import { ref,reactive } from 'vue';
let countRef = ref({ name:"blue" })
let count = reactive("") //error
const change = () => {
}
</script>
当给reactive中传入一个字符串时会报错,提示是传入的参数必须是object类型的,所以说不能传入字符串或者数字类型。
reactive的基本用法和ref差不多, 就是reactive只能定义object类型的变量。
- 修改reactive
<script setup lang="ts">
import { ref,reactive } from 'vue';
let count = reactive({ name:"orange" })
const change = () => {
count.name = "orange2"
console.log(reactive);
}
</script>
reactive修改不需要像ref一样,reactive不需要.value,直接修改就可以, 说明reactive和ref两个函数返回的类型是不一样的,有兴趣可以深入研究一下。
- shallowReactive
shallowReactive和shallowRef一样是浅拷贝的,可以参考shallowRef的内容
to系列全家桶
toRef
toRef的作用:抽离响应式对象中的某一个属性
toRef的使用:接收两个参数,第一个是对象,第二个是对象的一个key
这里分别通过抽离普通对象和响应式对象两种情况来详细介绍toRef的用法
<template>
<div>
<div>obj:{{ obj }}</div>
<hr>
<div>myLike:{{ myLike }}</div>
<button @click="change()">修改</button>
</div>
</template>
<script lang="ts" setup>
import { toRef,toRefs,toRaw } from "vue"
const obj = { //定义一个变量
name:"orange",
age:23,
like:['唱','跳','rap','篮球']
}
const myLike = toRef(obj,'like') //参数(对象,对象的其中一个key)
const change = ()=>{
console.log(myLike);
}
</script>
将obj对象中的like属性单独抽离出来, 然后我们进行修改操作看看结果如何
//抽离普通对象
<script lang="ts" setup>
import { toRef,toRefs,toRaw } from "vue"
const obj = { //普通变量
name:"orange",
age:23,
like:['唱','跳','rap','篮球']
}
const myLike = toRef(obj,'like') //参数(对象,对象的其中一个key)
const change = ()=>{
myLike.value = ['唱']
console.log(myLike);
}
</script>
myLike的值已经被修改了, 但是视图没有发生变化, 虽然like属性被抽离出来了,但是myLike不是响应式变量,因为obj对象不是响应式对象, 来试试抽离响应式对象看看视图会不会发生变化
//抽离响应式对象
<script lang="ts" setup>
import { reactive,toRef,toRefs,toRaw } from "vue"
const obj = reactive({ //响应式对象
name:"orange",
age:23,
like:['唱','跳','rap','篮球']
})
const myLike = toRef(obj,'like') //抽离对象like属性
const change = ()=>{
myLike.value = ['唱']
console.log(myLike);
}
</script>
会发现当抽离的对象是响应式对象时,修改抽离的变量视图是会发生改变的,所以得到结论是toRef的作用是抽离响应式对象中的某一个属性
toRefs
toRefs的作用:将响应式对象中的所有属性转换为单独的响应式数据
toRefs的用法:let { name,age,like } = toRefs(obj) 将整个对象传入作为参数
//修改普通对象
<template>
<div>
<div>obj:{{ obj }}</div>
<hr>
<div>{{ name }} - {{ age }} - {{ like }}</div>
<button @click="change()">修改</button>
</div>
</template>
<script lang="ts" setup>
import { reactive,toRef,toRefs,toRaw } from "vue"
const obj = { //区别在这
name:"orange",
age:23,
like:['唱','跳','rap','篮球']
}
let { name,age,like } = toRefs(obj) //区别在这
const change = ()=>{
name.value = 'orange2'
age.value = 24
console.log(name + '-', + age + '-' + like);
}
</script>
会发现和toRef一样修改普通对象时,修改值的时候视图是没有发生变化的。来看看响应式对象
//修改响应式对象
<template>
<div>
<div>obj:{{ obj }}</div>
<hr>
<div>{{ name }} - {{ age }} - {{ like }}</div>
<button @click="change()">修改</button>
</div>
</template>
<script lang="ts" setup>
import { reactive,toRef,toRefs,toRaw } from "vue"
const obj = reactive({ //区别在这
name:"orange",
age:23,
like:['唱','跳','rap','篮球']
})
let { name,age,like } = toRefs(obj) //区别在这
const change = ()=>{
name.value = 'orange2'
age.value = 24
console.log(name + '-', + age + '-' + like);
}
</script>
会发现视图是发生了改变的, 所以toRef和toRefs对于普通对象和响应式对象的情况是一样的,面对普通对象是无法将操作后的变量变成响应式的,所以一开始定义的对象就需要是响应式,二者的作用有一定的区别,在具体情况选择使用不同的方法。
//直接解构响应式对象,不使用toRefs
<template>
<div>
<div>obj:{{ obj }}</div>
<hr>
<div>{{ name }} - {{ age }} - {{ like }}</div>
<button @click="change()">修改</button>
</div>
</template>
<script lang="ts" setup>
import { reactive,toRef,toRefs,toRaw } from "vue"
const obj = reactive({ //区别在这
name:"orange",
age:23,
like:['唱','跳','rap','篮球']
})
let { name,age,like } = obj //区别在这
const change = ()=>{
name.value = 'orange2'
age.value = 24
console.log(name + '-', + age + '-' + like);
}
</script>
结果也是视图没有发生改变, 因为直接解构,解构后的变量不是响应式变量, 所以也是不满足响应式的
toRaw
toRaw的作用:将响应式对象转化为普通对象
toRaw的用法:let newObj = toRaw(obj) 将整个对象传入作为参数
<template>
<div>
<div>obj:{{ obj }}</div>
<hr>
<div>newObj:{{ newObj }}</div>
<button @click="change()">修改</button>
</div>
</template>
<script lang="ts" setup>
import { reactive,toRef,toRefs,toRaw } from "vue"
const obj = reactive({
name:"orange",
age:23,
like:['唱','跳','rap','篮球']
})
let newObj = toRaw(obj)
const change = ()=>{
console.log(obj);
console.log(newObj);
}
</script>
从打印的控制台可以看出,二者一个是响应式Reactive对象, 一个则是普通对象,让我们进行修改操作看看不同。
//只修改toRaw后的对象
<script lang="ts" setup>
import { reactive,toRef,toRefs,toRaw } from "vue"
const obj = reactive({
name:"orange",
age:23,
like:['唱','跳','rap','篮球']
})
let newObj = toRaw(obj)
const change = ()=>{
newObj.name = 'orange2'
console.log(obj);
console.log(newObj);
}
</script>
经过修改后发现视图没有进行更新, 证实了newObj是已经变为了普通对象。
//同时修改响应式对象和toRaw后的对象
<script lang="ts" setup>
import { reactive,toRef,toRefs,toRaw } from "vue"
const obj = reactive({
name:"orange",
age:23,
like:['唱','跳','rap','篮球']
})
let newObj = toRaw(obj)
const change = ()=>{
obj.name = 'orange3'
newObj.name = 'orange2'
console.log(obj);
console.log(newObj);
}
</script>
也是和之前的ref和shallowRef一样,是会受到响应式对象修改值的影响的, 所以不要同时修改响应式对象和进行toRaw操作后的对象。会造成视图更新的影响。
扩展
关于浅拷贝会受到深拷贝的影响的看法
- 根本原因:浅拷贝的对象和深拷贝的对象指向的是同一个地址
- 主要原因:深拷贝修改值时改变了地址指向的原始值
1、定义了一个obj对象,也定义了一个newObj对象,这时候值都是正常的
2、当obj对象被修改后,这时候原始定义的那个obj发生了改变
3、这时候newObj对象的值也发生了改变,因为原始的那个obj发生了改变, 而这个newObj又是从obj对象中衍生出来的,所以这时候已经发生了改变
4、那么这时候的newObj已经是修改后的obj对象再经过toRaw操作得到的结果了
5、然后因为深浅拷贝对响应式的影响,所以这时再去修改newObj对象, 视图也会发生改变
其实可以做一个很简单的实验, 就是再定义一个新的对象,obj2,newObj从obj2中进行toRaw操作,看看修改了原来的obj,这个newObj的值是否会发生改变。
<template>
<div>
<div>obj:{{ obj }}</div>
<hr>
<div>newObj:{{ newObj }}</div>
<button @click="change()">修改</button>
</div>
</template>
<script lang="ts" setup>
import { reactive,toRef,toRefs,toRaw } from "vue"
const obj = reactive({
name:"orange",
age:23,
like:['唱','跳','rap','篮球']
})
const obj2 = reactive({
name:"orange",
age:23,
like:['唱','跳','rap','篮球']
})
let newObj = toRaw(obj2)
const change = ()=>{
obj.name = 'orange3'
newObj.name = 'orange2'
console.log(obj);
console.log(newObj);
}
</script>
当他们指向不同时,就不会相互影响了,只是toRaw后的对象修改也会引起视图改变的影响依旧存在
其实就是值的指向问题,这里单纯是做个记录,具体还是看自己怎么理解吧,有问题望指出~~
四、总结
通过这篇文章已经大致了解到Vue3的基本变化和写法,已经可以创建项目开始实战啦,该系列后续会继续更新学习Vue3过程中的知识点和难点进行分享。