Vue3学习日志1--初识Vue3

303 阅读8分钟

image.png

前言

说明:本系列文章会持续更新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>

image.png

上面两种不同的写法会发现选项式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()里面可以放各种类型的变量,可以把变量打印出来看一看。 image.png

  • 修改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就无法响应式的改变属性中的值。 image.png

想要修改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不能一块写。

image.png

  • 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>

image.png

当给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>

image.png

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>

image.png

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>

image.png 会发现当抽离的对象是响应式对象时,修改抽离的变量视图是会发生改变的,所以得到结论是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>

image.png 会发现和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>

image.png 会发现视图是发生了改变的, 所以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>

image.png 结果也是视图没有发生改变, 因为直接解构,解构后的变量不是响应式变量, 所以也是不满足响应式的

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>

image.png

从打印的控制台可以看出,二者一个是响应式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>

image.png

经过修改后发现视图没有进行更新, 证实了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>

image.png 也是和之前的ref和shallowRef一样,是会受到响应式对象修改值的影响的, 所以不要同时修改响应式对象和进行toRaw操作后的对象。会造成视图更新的影响。

扩展

关于浅拷贝会受到深拷贝的影响的看法

  • 根本原因:浅拷贝的对象和深拷贝的对象指向的是同一个地址
  • 主要原因:深拷贝修改值时改变了地址指向的原始值

image.png 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>

image.png 当他们指向不同时,就不会相互影响了,只是toRaw后的对象修改也会引起视图改变的影响依旧存在
其实就是值的指向问题,这里单纯是做个记录,具体还是看自己怎么理解吧,有问题望指出~~

四、总结

通过这篇文章已经大致了解到Vue3的基本变化和写法,已经可以创建项目开始实战啦,该系列后续会继续更新学习Vue3过程中的知识点和难点进行分享。

系列视频

上一集:Vite+Vue3+Ts项目记录前言
下一集:Vue3学习日志2--computed和watch