1、vue产生的背景
新的 JavaScript 语言特性在主流浏览器中的受支持水平;其次是当前代码库中随时间推移而逐渐暴露出来的一些设计和架构问题.
- 利用新的语言特性(es6)
- 解决架构问题
参考:Vue3 常用知识点笔记_vue3知识点_DDDHL_的博客-CSDN博客
2、vue3有了解过吗?能说说跟vue2的区别吗?
- 体积减少:通过
webpack
的tree-shaking
功能,可以将无用模块“剪辑”,仅打包需要的。打包大小减少41%,内存减少54%。 - 速度更快:重写了虚拟
Dom
实现(diff算法),编译模板的优化(静态标记与事件缓存),初次渲染快55%, 更新渲染快133。 - 更易维护:基于
typescipt
编写,可以自动的类型定义提示,使用Composition API,高内聚,低耦合。使用Proxy代替defineProperty实现响应式,响仪式效果更好。 - 新的特性:新增组合API,常用的有setup、reactive、ref、toRefs;新增内置组件Fragment多跟节点,Teleport,Suspense(不确定),组件通信provide与inject
- 其他改变:响应式原理变化;新的生命周期钩子,data 选项应始终被声明为一个函数;新的脚手架工具vite;移除了一些东西,移除v-on的键盘修饰符(如@keydown.enter),移除过滤器filter。
3、响应式系统
3.1、ref函数
作用: 定义一个响应式的数据
语法: const xxx = ref(initValue)
创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
JS中操作数据: xxx.value
模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>
备注:
接收的数据可以是:基本类型、也可以是对象类型。
基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的。
对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。
也可以定义一个节点
<template>
<div ref="eleDom">ref-dom-test</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const eleDom = ref(null)
onMounted(() => {
console.log(eleDom.value.innerHTML) // ref-dom-test
})
return {
eleDom
}
},
}
3.2、reactive函数
作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
reactive定义的响应式数据是“深层次的”。
内部基于 ES6 的 Proxy 实现,通过Reflect代理对象操作源对象内部数据进行操作。
function reactive(obj) {
if (typeof obj !== 'object' && obj != null) {
return obj
}
// Proxy相当于在对象外层加拦截
const observed = new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
console.log(`获取${key}:${res}`)
return res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
console.log(`设置${key}:${value}`)
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
console.log(`删除${key}:${res}`)
return res
}
})
return observed
}
3.3、vue2与vue3响应式对比
vue2
中采用 defineProperty
来劫持整个对象,然后进行深度遍历所有属性,给每个属性添加getter
和setter
,实现响应式
vue3
采用proxy
重写了响应式系统,因为proxy
可以对整个对象进行监听,所以不需要深度遍历
- 可以监听动态属性的添加
- 可以监听到数组的索引和数组
length
属性 - 可以监听删除属性
3.4、reactive对比ref
- 从定义数据角度对比:ref用来定义:基本类型数据。reactive用来定义:对象(或数组)类型数据。备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象。
- 从原理角度对比:ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)。reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
- 从使用角度对比:ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value。reactive定义的数据:操作数据与读取数据:均不需要.value。
4、一般函数的使用
4.1、computed与nextTick的使用
//计算属性——简写
let fullName = computed(()=>{
return person.firstName + '-' + person.lastName
})
// 使用 nextTick 来在 DOM 更新后执行回调
nextTick(() => {
// 这里的代码会在 DOM 更新后执行
console.log('DOM 已更新');
});
4.2、watch
- 监视reactive定义的响应式数据整个对象时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据中某个属性时:deep配置有效。
//情况一:监视ref定义的响应式数据
watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
},{immediate:true})
//情况二:监视多个ref定义的响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变化了',newValue,oldValue)
})
/* 情况三:监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
*/
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效
//情况四:监视reactive定义的响应式数据中的某个属性
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//情况五:监视reactive定义的响应式数据中的某些属性
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
4.3、watchEffect函数
watch的套路是:既要指明监视的属性,也要指明监视的回调。
watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
watchEffect有点像computed:但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(()=>{
const x1 = sum.value
const x2 = person.age
console.log('watchEffect配置的回调执行了')
})
5、生命周期
Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
beforeDestroy改名为 beforeUnmount
destroyed改名为 unmounted
Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
beforeCreate===>setup()
created=======>setup()
beforeMount ===>onBeforeMount
mounted=======>onMounted
beforeUpdate===>onBeforeUpdate
updated =======>onUpdated
beforeDestroy==>onBeforeUnmount
destroyed=====>onUnmounted
6、toRef与toRefs
toRef针对一个响应式对象的prop,创建一个ref,具有响应式,两者保持引用关系
通过toRefs将其转为普通对象并返回,两者保持引用关系。这时候可以结构赋值,并且值是响应式的。这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。
let info = reactive({
name: 'Tony',
greet: 'Hello'
})
let rGreet = toRef(info, 'greet') //
更改 rGreet const onChangeGreet = () => { rGreet.value = 'world!' }
// 复制整个 info
let rInfo = toRefs(info)
// 更改 rInfo.greet
const onChangeGreet = () => {
rInfo.greet.value = 'world!'
}
为什么需要toRef和toRefs
- 初衷: 在不丢失响应式的前提下,对对象数据进行解构
- 前提: 针对的是响应式对象,不是普通对象
- 结果: 不创造响应式,只延续响应式
const state = reactive({
foo: 1,
bar: 2
})
// state 是响应式对象,但是foo只是一个值
const { foo, bar } = state; // 此时 foo 就等于 1 了,1 是一个值,不是一个响应式对象
foo = 6;
解构基本类型会失去响应式,那如果是引用类型不会。
7、provide 与 inject
父组件有一个 provide
选项来提供数据,后代组件有一个 inject
选项来开始使用这些数据
setup(){
......
let car = reactive({name:'奔驰',price:'40万'})
provide('car',car)
......
}
setup(props,context){
......
const car = inject('car')
return {car}
......
}
8、Teleport
Teleport
是一种能够将我们的组件html结构移动到指定位置的技术。允许开发者在任何地方渲染组件,而不仅仅是在它们声明的DOM范围内。Teleport使得在各种情况下渲染组件变得更加灵活。
<template>
<div>
<button @click="showModal = true">Show Modal</button>
<teleport to="#id">
<div v-if="showModal" class="modal">
<h2>Modal Header</h2>
<p>Modal Content</p>
<button @click="showModal = false">Close Modal</button>
</div>
</teleport>
</div>
</template>
9、Suspense
-
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
异步引入组件
import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
使用Suspense包裹组件,并配置好default与 fallback
<template>
<div class="app">
<h3>我是App组件</h3>
<Suspense>
<template v-slot:default> // 加载完显示
<Child/>
</template>
<template v-slot:fallback> // 未加载完显示
<h3>加载中.....</h3>
</template>
</Suspense>
</div>
</template>
10、编译阶段
回顾Vue2
,我们知道每个组件实例都对应一个 watcher
实例,它会在组件渲染的过程中把用到的数据property
记录为依赖,当依赖发生改变,触发setter
,则会通知watcher
,从而使关联的组件重新渲染。Vue3
在编译阶段,做了进一步优化,diff算法优化,静态提升。事件缓存
vue3
在diff
算法中相比vue2
增加了静态标记,已经标记静态节点( PatchFlag
静态标记值为 -1 ,)的p
标签被放置在render
函数外,在diff
过程中则不会比较,把性能进一步提高。
Vue3
中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用
这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操作,优化了运行时候的内存占用。
源码体积
Vue3
整体体积变小了,除了移出一些不常用的API,再重要的是Tree shanking
任何一个函数,如ref
、reavtived
、computed
等,仅仅在用到的时候才打包,没用到的模块都被摇掉,打包的整体体积变小
11、v-model 升级
v-model
的组件通信原理涉及父组件通过 v-model
指令传递数据给子组件,子组件接收数据并触发事件,父组件监听事件并更新数据。这种方式使得自定义组件可以与 v-model
一样简单地处理数据的双向绑定。
<template>
<div>我是父组件</div>
<pre>我是父子组件同步的数据:{{ pageNo }} --- {{ pageSize }}</pre>
<Child v-model:pageNo="pageNo" v-model:pageSize="pageSize"></Child>
</template>
<script setup>
import { ref } from "vue";
import Child from "./Child.vue";
const pageNo = ref(10000);
const pageSize = ref('hello vue!')
</script>
<template>
<div>我是子组件</div>
<pre>父组件给我的数据:{{ pageNo }} --- {{ pageSize }}</pre>
<button @click="() => $emits('update:pageNo', pageNo + 1000)">点我改变接收到的数据1</button>
<button @click="() => $emits('update:pageSize', 'hello world!')">点我改变接收到的数据2</button>
</template>
<script setup>
const props = defineProps(['pageNo', 'pageSize'])
const $emits = defineEmits(['update:pageNo', 'update:pageSize'])
</script>
组件通信
参考:Vue3---组件通信(笔记) - 掘金 (juejin.cn)
11、创建 Vue3 工程
## 创建 vue create vue_test
## 创建工程 npm init vite-app <project-name>
13、设计原则
https://kdocs.cn/l/chOXAO8mLgYr
Vue3实践SOLID五大设计原则
Vue3实践SOLID五大设计原则_vue solid_码农阿焦的博客-CSDN博客
SOLID原则是面向对象编程和面向对象设计的5大基本原则
- S:SRP单一职责原则
- O:OCP开放封闭原则
- L:LSP里氏替换原则
- I:ISP接口隔离原则
- D:DIP依赖倒置原则
14、vue 中使用了哪些设计模式
浅谈Vue项目中用到的设计模式_vue设计模式_其人美且偲的博客-CSDN博客
什么是设计模式:
设计模式的原则是找出程序中的变化,并将变化封装起来,实现高效的可复用性。核心在于意图,而不在结构。通过设计模式可以帮助我们增强代码的可重用性、可扩充性、 可维护性、灵活性。我们使用设计模式的最终目的是为了实现代码的高类聚和低耦合。你是否思考过这样的一个问题,如何让代码写的更有健壮性,其实核心在于把握变与不变。确保变的部分更加灵活,不变的地方更加稳定,而使用设计模式可以让我们达到这样的目的。
设计模式是在软件开发中用于解决特定问题的通用解决方案。它们有助于开发者编写易于理解、可维护和可扩展的代码。设计模式可以分为以下几种主要类别:
1. **创建型模式(Creational Patterns):** 这些模式关注对象的创建机制,以确保系统在实例化对象时能够更加灵活和高效。包括:
- 单例模式(Singleton Pattern)
- 工厂模式(Factory Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
2. **结构型模式(Structural Patterns):** 这些模式关注对象之间的组合,以构建更复杂的结构。它们有助于实现不同对象之间的合作关系。包括:
- 适配器模式(Adapter Pattern)
- 桥接模式(Bridge Pattern)
- 组合模式(Composite Pattern)
- 装饰器模式(Decorator Pattern)
- 外观模式(Facade Pattern)
- 享元模式(Flyweight Pattern)
- 代理模式(Proxy Pattern)
3. **行为型模式(Behavioral Patterns):** 这些模式关注对象之间的交互和分配责任,以实现更灵活的通信和协作。包括:
- 责任链模式(Chain of Responsibility Pattern)
- 命令模式(Command Pattern)
- 解释器模式(Interpreter Pattern)
- 迭代器模式(Iterator Pattern)
- 中介者模式(Mediator Pattern)
- 备忘录模式(Memento Pattern)
- 观察者模式(Observer Pattern)
- 状态模式(State Pattern)
- 策略模式(Strategy Pattern)
- 模板方法模式(Template Method Pattern)
- 访问者模式(Visitor Pattern)
4. **并发模式(Concurrency Patterns):** 这些模式关注多线程和并发编程,以解决多线程环境中的各种问题。包括:
- 同步模式(Synchronization Patterns)
- 互斥模式(Mutex Patterns)
- 协作模式(Coordination Patterns)
5. **架构模式(Architectural Patterns):** 这些模式关注整体系统架构和组织,以支持系统的扩展性、可维护性和性能。包括:
- 模型-视图-控制器(MVC)模式
- 分层架构模式
- 微服务架构模式
每种设计模式都有其特定的应用场景和用途,开发者可以根据问题的性质选择适当的模式来解决问题。设计模式是软件工程中的重要概念,可以帮助开发者编写高质量的代码并降低维护成本。
1.发布-订阅模式 ((响应式数据原理))
发布订阅者模式又叫观察者模式,发布订阅者模式一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得将得到通知;观察者模式则是一种一对一的依赖关系。
2.工厂模式 - 传入参数即可创建实例
工厂模式:工厂模式是用来创建对象最常见的一种设计模式。不必暴露构造函数的具体逻辑,而是将逻辑封装在一个个函数之中,那么这个构造函数就可以被看做工厂。
场景: 有构造函数的地方,写了大量的构造函数代码,调用了大量的new操作符。
优点:通过工厂模式,我们可以快速创建大量相似对象,没有重复代码。
缺点:工厂模式创建的对象属于Object,无法区分对象类型,这也是工厂模式没有广泛使用的原因。
参考vue
虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode
虚拟DOM的VNode_dom节点上的 __vnode没有了_Tand.的博客-CSDN博客
3.单例模式 - 整个程序有且仅有一个实例
单例模式:确保一个类只有一个实例对象,并提供一个全局访问点供其访问。
优点:适用于单一对象,只生成一个对象实例,避免频繁创建和销毁实例,减少内存占用。
缺点:不适用动态扩展对象。
场景:登录浮窗、Vue中的axios实例(我们对axios进行请求拦截和响应拦截,多次调用封装好的axios但是仅设置一次,封装好的axios导出就是一个单例)、全局态管理 store、线程池、全局缓存
4、装饰器模式
构造函数 原型对象
装饰器模式(切面编程AOP): 在不改变对象自身的基础上,在程序运行期间给对象动态的添加职责;若直接修改函数体,则违背了’开放封闭原则’,也违背了我们的’单一职责原则’;简单的说就是允许向现有的函数添加新的功能,同时不改变其结构。
场景: vue中的表单验证与表单提交就运用了这种模式,遵循封闭开放原则。
5.策略模式
策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略
不同环境---不同模式