❤️ Author: 老九
☕️ 个人博客:老九的CSDN博客
🙏 个人名言:不可控之事 乐观面对
😍 系列专栏:
文章目录
- Vue3
- data属性
- 插值语法
- 修饰符
- v-model
- v-for
- 虚拟DOM
- diff算法
- 响应式原理
- v-for中的key
- nextTick
- composition API
- setup函数
- reactive函数
- readonly函数
- toRefs、toRef函数
- computed
- ref获取元素方式
- watch函数
- 记住滚动条位置的封装
- script setup语法糖
Vue3
- MVC和MVVM模型
MVC是Model-View-Controller,MVVM是Model-View-ViewModel,html就相当于是view,javaScript就相当于controller,通过axios获取数据,获取的数据就是model,然后通过model返回给view,这样就是MVC模式;
Vue就是MVVM的结构,ViewModel就是将模型里的数据绑定到View上了,如果view上发生了一些事件,通过ViewModel触发DOM Listeners之后操作model里面的值
data属性
- Vue2中可以不写函数,Vue3的版本必须的是一个函数,Vue2通过Object.defineProperty实现,Vue3通过new Proxy实现
- 注意数组更新的检测,如果是使用一些原生的数组方法,是直接修改了数组的值,如果是使用map,filter等高阶函数,会生成一个新的函数,不会修改原函数的值
插值语法
- 双花括号中不能插入html代码,为了避免xss的攻击,通过v-html可以实现
修饰符
- prevent修饰符会告知v-on指令对触发事件调用event.preventDefault(),用来阻止事件的默认行为,例如a标签默认跳转连接等等
- enter修饰符,自动添加回车确定事件
- passive:被动事件表示该事件处理函数不会调用preventDefault(),浏览器可以不必等到这个函数运行完才执行默认行为,可以让默认行为更早的发生,让页面看起来更流畅
- once:运行一次旧销毁
v-model
v-for
- 在使用v-for进行列表渲染的时候,通常会和key属性一起使用,key属性主要用于Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes
- 首先认识一下VNode,VNode就是虚拟节点,无论是组件还是元素,他们最终在Vue中表示出来的都是一个个VNode,VNode本质是一个JS对象
- 元素先转化成VNode,然后VNode之后转换成真实的DOM
- 那么如果我们不只是一个简单的div,而是有一大堆的元素,那么他们应该会形成一个VNode Tree,多个VNode形成一个虚拟DOM
- 那么为什么有一个虚拟DOM呢,方便我们后续的diff算法,也方便我们跨平台
虚拟DOM
- 虚拟DOM就在Vue的实例上面,叫做_vnode,首先tag是根元素div,div的id在data中的attrs展示是container,里面有一个children,里面就是一些子节点(包括文本节点)
- vue的模板字符串编译成一个模板函数,vue里面自带一个函数可以实现这个,vue.compile('div @click=“foo()”></ div>),这个函数里面还有一个cache缓存函数,如果转化过了就不转化了,原理就是先通过模板生成一个抽象语法树 ,然后根据抽象语法树变成模板函数(这是一个匿名函数 ,通过With关键字传this),然后模板函数获得值再返回给你,这个东西才是我们真正可以用的东西,这个模板函数就是render函数,render 函数是用于生成组件的虚拟 DOM(Virtual DOM)的函数。
- 当修改界面上的东西的时候,就会在render函数中调用这句话创建一个新的vnode节点,这句话vm._renderProxy是vue对象,后面的createElement就相当于elt函数,这个函数里面有一个_c通过这个_c函数就可以创建虚拟DOM节点,_v是创建文本节点,_c传进去三个参数,标签名,属性attrs,子节点
- 当我们创建完新的虚拟DOM节点之后,就会调用vm._update函数
- 把旧的vnode赋值给prevVnode,然后把新的vnode赋值给vm._vnode
-如果是第一次渲染,就直接创建真实的DOM节点,然后在页面上展示,如果不是第一次渲染,通过vm_patch_函数,进行打补丁,将两者的差异在真实的DOM上打上补丁,之后进行更新,在patch函数中有一个updateChildren函数,主要目的是对比新旧子节点并进行高效地更新
diff算法
- 我们先在有一个案例,假如当我点击按钮的时候,会在一个数组中间加一个f,然后通过v-for展示出来,在Vue中,对于相同给父元素的子元素节点并不会重新渲染整个列表,因为对于列表中abcd,他们都是没有变化的,在操作真实DOM的时候,我们只需要在中间插入一个f的li即可
- Vue中对于有key和没有key会调用两个不同的方法,如果有key,就会使用patchKeyedChildren方法,如果没有key,就会使用patchUnkeyedChildren方法
- 子节点更新策略:
四种命中查找
1.新前与旧前
2.新后与旧后
3.新后于旧前
4.新前于旧后
先准备四个指针,旧前,旧后,新前,新后,首先对比新前与旧前是不是一个节点,如果相同就不执行节点移动操作,命中了一种就不再进行命中判断了,旧前指针往下移动,新前指针也往下移动,循环的条件就是新前<=新后 && 旧前<=旧后,如果旧节点先循环完毕了,说明新节点是有剩余的,就说明了新前和新后之间的节点都是新增的节点,这是新增的情况
如果是删除的情况,前前比较,命中,指针下移,直到前前比较,不命中,让后后比,命中,指针都上移动,如果新节点先循环完毕,说明老节点中还有剩余节点,说明旧前和旧后之间卡住的节点就是他们是要被删除的节点
如果都没有命中,就需要用循环来寻找,循环旧节点秒如果找到了,虚拟DOM就会给该节点打上undefined,但是真实DOM就会移动位置到旧前之前的位置
当新前与旧后命中的时候,就会移动新前指向的这个节点在老节点中对应的节点,把老节点中这个节点移动到旧前的前面,然后新前往后移,旧后往前移
当新后和旧前命中的时候,就会移动新后指向的这个节点在老节点中对应的节点,把老节点中这个节点移动到旧后的后面
响应式原理
- v2是通过getter和setter,v3是通过proxy对象修改,通过代理对象,当对对象继续操作之后,先告诉proxy,由proxy进行操作
- v3中数据的操作都是通过proxy的,原来的对象还是不变的,变的都是proxy对应的代理对象
v-for中的key
- 文档中说,vue默认是按照“就地更新“的策略来更新通过v-for渲染的元素列表,当数据项的顺序改变时,vue不会移动DOM元素的顺序,而是就地更新每个元素,确保他们在原本指定的索引位置上渲染。
nextTick
- vue 中的 nextTick 主要用于处理数据动态变化后,DOM 还未及时更新的问题,用nextTick 就可以获取数据更新后最新 DOM 的变化。 nextTick 是将回调函数推入微任务队列中,以便在当前宏任务执行完毕后执行。它能够在 Vue 完成一次 DOM 更新后执行回调函数,用于处理与 DOM 更新相关的操作。
- 在旧版浏览器中,通过MutationObserver可以监控DOM对象的变化,可以监听属性attributes,子代节点childList
composition API
- 在Vue2中,我们编写组件的方式是OptionsAPI,OptionsAPI一大特点就是在对应的属性中编写对应的功能模块,比如data定义数据,methods定义方法
- 但是这种代码有很大的弊端:当我们实现某一个功能的时,这个功能对应的代码逻辑会被拆分到各个属性中,当我们的组件变得更大更复杂的时候,同一个功能的逻辑就会被拆分的很分散。
- 如果我们能够将同一个逻辑关注点的代码收集在一起会更好。
- 这个就是Composition API想做的事情,这个API我们就要在setup函数中编写。
setup函数
- setup函数有两个参数,第一个参数是props,第二个参数是context
- props其实就是父组件传递过来的属性会被放到props对象中,我们在setup中如果需要使用,那么就可以直接通过props参数获取,我们的setup中是没有this的
- context是一个对象,有三个属性,attrs,slots,emit,slots是父组件传递过来的插槽,emit是我们组件内部需要发出事件的时候,会用到emit(但是不可以通过this.$emit发出事件)
<template>
<div class="app">
<h2>当前计数:{{ counter }}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
let counter = ref(100);
const increment = () => {
counter.value += 1;
};
const decrement = () => {
counter.value -= 1;
};
return {
counter,
increment,
decrement,
};
},
};
</script>
<style lang="scss">
</style>
- ref是将数据变成响应式
reactive函数
- 用于定义复杂类型的数据
- ref用于定义简单类型的数据,也可以定义复杂的数据
<template>
<div>message:{{ message }}</div>
<button @click="changeMessage">修改message</button>
<hr />
<h2>账号:{{ account.username }}</h2>
<h2>密码:{{ account.password }}</h2>
<button @click="changAccount">修改账号</button>
</template>
<script>
import { ref, reactive } from "vue";
export default {
setup() {
let message = ref("hello world");
const changeMessage = () => {
message.value = "你好,世界";
};
const account = reactive({
username: "coderwhy",
password: "12345",
});
const changAccount = () => {
account.username = "limingpu";
};
return { message, changeMessage, account, changAccount };
},
};
</script>
<style>
</style>
readonly函数
- 如果要在子组件中修改父组件的内容,需要通过emit函数,在父组件中进行操作,emit在setup函数中的centext参数
- 但是在vue3中,专门设计了一个readonly函数,用readonly函数包过的数据,是修改不了的,只能是从子组件将要修改的数字传到父组件,然后修改父组件中用readOnly包裹的值即可
toRefs、toRef函数
- 当我们有一个被reactive包裹的对象,我们想对其中的元素进行解构,解构后的值就失去了双向绑定的特性,这时候我们就需要用toRefs函数将reactive包裹的对象的值再进行包裹,解构后的值才能实现响应式,如果想结构其中一个字段,通过toRef函数就可以实现了
computed
- computed值返回的是一个ref,所以需要.value
ref获取元素方式
- 挂载函数变成onMounted了,如果要获取ref元素,ref=’titleRef‘,然后再setup函数中const titleRef = ref(),获取的时候点value就可以了
watch函数
记住滚动条位置的封装
script setup语法糖
- 在script旁边加一个setup,就可以直接使用composition API,也不需要返回了。
- 更少的样板内容,更简洁的代码;更好的运行时性能。
- 导入的组件可以直接使用,不需要components
- 定义props通过defineProps,这个函数不需要导入,就在当前的作用域中,如果需要发送事件,通过defineEmits([])
- 如果想拿到一个组件的实例,通过ref,也就是所谓的父组件想用子组件中的东西,需要在子组件中通过defineExpose函数暴露给父组件才可以使用
- 子组件
- 父组件
————————————————————————
♥♥♥码字不易,大家的支持就是我坚持下去的动力♥♥♥
版权声明:本文为CSDN博主「亚太地区百大最帅面孔第101名」的原创文章