深入学习Vite+Vue3心得笔记| 青训营

91 阅读7分钟

项目初始化

1.vite项目构建

构建项目 npm init vite@latest (脚手架构建npm init vue@latest 特点:配置更多)

选择框架为VUE

选择语言为TS

在目录中就会产生一个Vue3的初始化项目

2.安装依赖

在终端执行npm install

他会扫描package.json里的devDependencies内的依赖并进行安装,生成node_modules

目录详解以及插件

public存放静态资源,不会被vite编译

src--assets存放静态资源

componets存放组件

app.vue入口文件

main.ts全局配置文件

vite-env.d.ts声明文件扩充 让ts认识.vue后缀的文件

index.html使用html做入口文件,使用esm引入

package.json命令以及依赖

tsconfig.json TS的配置文件

vite.config.json vite的配置文件

1.SFC单文件组件(APP.VUE)介绍

setup的script标签只能有一个

template也只能有一个

2.npm run dev执行过程

1.先找本地的node_modules找bin里的可执行的vite

2.没有的话,找npm install -g全局包找vite

3.没有的话,找环境变量Path

4.都没有,报错

VUE3语法及指令

1.Setup

常规写法 :必须对外暴露才能用

<script>
export default {
  setup() {
    const n=1;
    return {
      n
    }
  }
}
</script>

结合TS的setup语法糖写法:不用暴露

<script setup lang="ts">
const n:number=1
</script>

2.模版语法{{ }}

支持数值运算,三元表达式,以及API的运算

<template>
 <div>
  {{ n+1 }} //2
  
  {{ n?1:2 }} //1
  
  {{ m.map(v=>({'数字':v})) }}  //  [ { "数字": 1 }, { "数字": 2 }, { "数字": 3 } ]
 </div>
</template>

<script setup lang="ts">
const n:number=1
const m:number[]=[1,2,3]
</script>

3.V-指令

vue

  • v-text 用来显示文本
  • v-html 用来展示富文本
  • v-if 用来控制元素的显示隐藏(切换真假DOM)
  • v-else-if 表示 v-if 的“else if 块”。可以链式调用
  • v-else v-if条件收尾语句
  • v-show 用来控制元素的显示隐藏(display none block Css切换)
  • v-on 简写@用来给元素添加事件
  • v-bind 简写: 用来绑定元素的属性Attr
  • v-model 双向绑定
  • v-for 用来遍历元素
  • v-on修饰符 冒泡案例
  • v-once 性能优化只渲染一次
  • v-memo 性能优化(小) 配合v-for控制渲染

动态事件绑定

<template>
<button @[event]="xxx">点击我</button>
</template>

<script setup lang="ts">
const event='click'
const xxx=()=>{
    console.log('haha');
}
</script>

介绍虚拟DOM与AST

虚拟DOM就是通过JS来生成一个AST节点树,使用Js来描述Dom对象

虚拟DOM产生的原因:DOM自身有很多的属性,直接操作dom的话,会非常影响性能,因此就用js来描述dom

AST节点树用途很广泛:

  • Babel插件ES6转ES5
  • TS转JS
  • JS通过v8引擎转字节码

VUE3的diff算法

场景就是v-for的key值

<template>
  <div>
    <div :key="item" v-for="(item) in Arr">{{ item }}</div>
  </div>
</template>
 
<script setup lang="ts">
const Arr: Array<string> = ['A', 'B', 'C', 'D']
Arr.splice(2,0,'DDD')
</script>

主要的 diff 过程如下:

  1. 如果新旧 vnode 的 key 不相等,直接认定为不同节点,无需继续比较,删除旧节点,插入新节点。

  2. 如果新旧 vnode 的 key 相等,进一步比较以下几个方面:

    • 检查是否存在静态节点,如果存在,则跳过其子节点的比较(即不进入深度遍历),因为静态节点不会发生变化。
    • 检查是否设置了 PatchFlag,即编译时标记。PatchFlag 用于表示当前节点的子节点是否有变化。
    • 当前节点无子节点时,直接返回。如果有子节点,依次对比子节点。
    • 对比子节点时,首先根据 key 在新旧子节点列表中找到相同 key 的节点,然后递归比较这些节点。如果找不到相同 key,表示为新增节点,直接插入。如果新旧节点都存在相同 key,则递归比较这些节点。
    • 对比过程会继续递归进行,直到对比完所有的子节点。
  3. 在 diff 过程中,会根据变化情况生成 PatchFlag,并记录下需要进行具体操作的类型,如更新、删除或插入等。

  4. 最后,根据生成的 PatchFlag,执行具体的 DOM 操作进行更新。

Vue 3 的 PatchFlag 算法相对于 Vue 2.x 的 Virtual DOM diff 算法,在处理较大的 vnode 树时,能够更高效地减少不必要的比较和更新操作,提升性能和渲染速度。

区别于VUE2的diff算法:

VUE2会先前序对比,然后后序对比,最后会进行前后交叉对比

Ref全家桶

  • 作用: 定义一个响应式的数据
  • 语法: const xxx = ref(Value)
    • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
    • 一般支持普通类型的数据,但是当传入引用类型的数据时,底层也会调用reactive
    • JS中操作数据: xxx.value
    • 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>
  • 泛型:
<template>
{{ man }}
<button @click="change"></button>
</template>

<script setup lang="ts">
import {ref} from 'vue'
import type {Ref} from 'vue'
// 使用Ref规定泛型
type M={
    name:string,
    age:number
}
const man:Ref<M>=ref({
    name:"张三",
    age:18
})
const change=()=>{
    man.value.name="李四"
}
</script>

<style scoped>

</style>
  • isRef 判断是不是一个ref对象
import { ref, Ref,isRef } from 'vue'
let message= ref("message")
let noRef:number = 123
const changeMsg = () => {
  console.log(isRef(message)); //true
  console.log(isRef(noRef)); //false
}
  • shallowRef 浅层次的响应式,底层不会调用toReactive(只能响应到.value这个层次)

ref是深层次的响应式,可以响应每一层的数据

并且ref不能和shallowRef混用,前者会影响后者的响应范围

<script setup lang="ts">
import { ref, shallowRef } from 'vue'

let message = shallowRef({
  name: "小"
})
页面的name没有变化
const change1 = () => {
  message.value.name = '大'
}
页面的name发生了变化
const change2 = () => {
  message.value = {
  name:'大'
  }
}
</script>
  • triggerRef 强制更新页面DOM

因为ref的底层就调用了triggerRef,而shallowRef却没有,因此不能混用

可以使用triggerRef 强制 shallowRef更新页面

const change = () => {
  message.value.name = '大'
 triggerRef(message)
}
  • customRef 自定义Ref 自己加对应的逻辑 (防抖什么的)

提供 track 和 trigger接口,来重写get和set

track 收集依赖

trigger触发依赖

<template>
  <div>
    {{ name }}
  <button @click="change">修改customRef</button>
  </div>
</template>
 
<script setup lang='ts'>
import { ref, customRef } from 'vue'
 //自定义myref
function myRef<T = any>(value: T) {
  return customRef((track, trigger) => {
    return {
      get() {
        //具体的操作  
        track()
        return value
      },
      set(newVal) {
        //具体的逻辑,比如写一个定时器来实现防抖  
		value = newVal
        trigger()
      }
    }
  })
}
//调用自定义的myRef
const name = myRef<string>('张三')

const change = () => {
  name.value = '李四'
}
  • 获取DOM :取代document.querySelector等一系列用法
  • 打的 ref 可以用 this.$refs 获取到
<template>
<!-- 给div打个ref标记 -->
<div ref="div1"></div>
<button @click="changes">查看</button>
</template>

<script setup lang="ts">
import {ref} from 'vue'
//<HTMLDivElement>是用来指定ref的类型的
const div1=ref<HTMLDivElement>()

const changes=()=>{
//读取打了ref标记的标签里的内容 
console.log(div1.value?.innerText); //?就是可能没有值,可能有
}
</script>
  • 补充:
    • 接收的数据可以是:基本类型、也可以是对象类型。
    • 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的。
    • 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。

Reactive全家桶

  • 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)

在 Vue 中,Reactive(响应式)是指当数据发生变化时,相关的界面会自动更新以反映数据的最新状态。Vue 使用了一种叫做 "Reactivity System" 的机制来实现响应式。

Reactivity System 的核心是 Vue 实例中的 data 对象或组件中的 data 选项。当你将一个属性添加到 data 对象中时,Vue 会将其转换为响应式属性。这意味着当该属性的值发生变化时,与之相关的视图会自动更新。

Vue 2.x 中使用的是 Object.defineProperty 方法来实现属性的 getter 和 setter,而 Vue 3 中则引入了 Proxy 对象来实现响应式。Proxy 是 ES6 中的一个特性,它可以拦截并劫持 JavaScript 对象的操作,使我们能够监听属性的变化并执行相应的操作。

  • 语法:const 代理对象= reactive(源对象)

    • 接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
  • reactive只支持引用类型 Array Object Map Set,不能绑定普通类型的数据(报错)

  • reactive不需要.value来操作值

<template>
{{ obj }}
<button @click="change">点我年龄+1</button>
</template>

<script setup lang="ts">
import { reactive } from 'vue';
const obj=reactive({name:'张三',age:18})
const change=()=>{
    obj.age++
}
</script>
  • reactive定义的响应式数据是“深层次的”。

  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。

  • 使用数组的reactive时,不能直接给数组整个赋值,这样会覆盖掉数组的Proxy,使数组丢失响应式

    <template>
    <ul>
        <li v-for="item in list">{{item}}</li>
    </ul>
    <button @click="add">点我添加</button>
    </template>
    <script setup lang="ts">
    //这样赋值页面是不会变化的因为会脱离响应式
    let list = reactive<number[]>([])
    const add=()=>{
      setTimeout(() => {
      	list = [1, 2, 3]
      	console.log(list);
    	},1000)
    }
    </script>
    

解决方案1 使用push+解构赋值

<script setup lang="ts">
import { reactive } from 'vue'
let list = reactive<number[]>([])
setTimeout(() => {
  const arr = [1, 2, 3]
  list.push(...arr)
  console.log(list);
},1000)
</script>

解决方案2 包裹一层对象

原理是,把数组当作对象的一个属性值,直接修改对象的属性值是被允许的,但是上面遍历的区域就要改成list.arr

<script setup lang="ts">
type list = {
  arr?:Array<number>
}
let list = reactive<list>({
   arr:[]
})
setTimeout(() => {
  const res = [1, 2, 3]
  list.arr = res;
  console.log(list.arr);
  
},1000)
</script>
  • Readonly 拷贝一份proxy对象将其设置为只读

tips:给readonly的那个变量赋值时,会报错,但是给原数据reactive赋值却是可以的

import { reactive ,readonly} from 'vue'
const person = reactive({count:1})
const copy = readonly(person)
copy.count++  //报错
person.count++  //count+1
  • shallowReactive 浅层次的响应式,底层不会调用toReactive(只能响应到.first 第一个属性值 这个层次)

reactive是深层次的响应式,可以响应每一层的数据

并且reactive不能和shallowReactive混用,前者会影响后者的响应范围

To全家桶

  • toRef 把某个属性转化为响应式的

对非响应式的数据没有任何作用(数据变,视图不变)

**应用场景:**请求拿到一个对象,但是只使用其中的某个属性,并且需要是响应式的

<script setup lang="ts">
import { reactive, toRef } from 'vue'
 
const obj = {
   foo: 1,
   bar: 1
}
const state = toRef(obj, 'bar')
// bar 转化为响应式对象
</script>
  • toRefs 把引用类型里的所有的属性都转化为响应式的

**底层原理: **用for循环遍历整个对象,把每个属性都调用一次toRef

使用场景: 拿到对象后的解构赋值

import { reactive, toRefs } from 'vue'
const obj = reactive({
   foo: 1,
   bar: 1
})
let { foo, bar } = toRefs(obj)
foo++,bar--;
console.log(foo, bar);
  • toRaw 去响应式(脱离Proxy)

    const state = toRaw(obj) // 响应式对象转化为普通对象

底层原理:

const toRaw = ()=>{
	data['__v_raw']
}

响应式原理

  • Vue2 使用的是 Object.defineProperty

  • vue2响应式存在的问题

    • 对象只能劫持预先定义好的属性,新增的属性无法进行劫持,要调用Vue.$set (XXX)
    • 数组也只是重写了7个方法,直接修改数组的值,也是无法劫持的
    • 虽然可以解决数组的问题,但是性能的压力比较大
  • vue3使用的是 Proxy

    • Vue3 的响应式原理依赖了 Proxy 这个核心 API,通过 Proxy 可以劫持对象的某些操作
  • VUE3响应式源码实现

    • reactive和effect的实现

    底层函数 : effect track trigger

浅浅谈一下Vue2到Vue3都有哪些进步

  1. 响应式系统的改进:

在 Vue 2 中,使用了 Object.defineProperty 来实现属性的响应式绑定,而 Vue 3 则采用了更高效和强大的 Proxy 对象来实现响应式。这使得 Vue 3 的响应式系统性能更好,并支持了更多的用例,如检测数组变化、动态添加属性等。

  1. Composition API:

Vue 3 引入了 Composition API,它提供了一种新的组织逻辑代码的方式。与 Vue 2 的 Options API 相比,Composition API 更加灵活、可组合和易于维护。通过 Composition API,可以将相关的逻辑代码组织成逻辑组合函数,使代码更具可读性和可复用性。

  1. 更好的 TypeScript 支持:

Vue 3 在设计上更注重对 TypeScript 的支持,提供了更好的类型推导和类型定义。Vue 3 的源码也经过重写,使得它更易于在 TypeScript 项目中进行开发。

  1. 更小的体积:

Vue 3 的运行时代码经过优化,相比 Vue 2 体积更小,加载速度更快。

  1. 虚拟 DOM 的改进:

Vue 3 中重新设计了虚拟 DOM 的实现,采用了更高效的算法和数据结构,提升了渲染性能。

  1. 脚手架工具的改进:

Vue CLI 4 是基于 Vue 2 的版本,而 Vue CLI 5 是基于 Vue 3 的版本。Vue CLI 5 提供了许多新特性和工具链的改进,使得开发和构建 Vue 项目更加便捷和高效。

总结

学习本身是一件比较枯燥的事情,Vue3相较于Vue2的难度,也提升了很多,不是我们一天两天就能学会的,而且学习的过程中需要反复操作和实践。因此,在学习Vue3时,我们要合理安排好时间,比如每天上午一到两个小时用来学习Vue3,下午用来做实验和练习。每星期可以有两个晚上用来巩固所学知识,这样才能更好地掌握所学的知识和技能。