vue2-vue3面试
v-text/v-html/v-once/v-show/v-if/v-for/v-bind/v-on
beforeCreate()
- 已有DOM节点:可以
- data选项:不可以
- 虚拟DOM节点:不可以
created():掌握
- 已有DOM节点:可以
- data选项:可以
- 虚拟DOM节点:不可以
beforeMount()
- 已有DOM节点:可以
- data选项:可以
- 虚拟DOM节点:不可以
mounted():掌握
- 已有DOM节点:可以
- data选项:可以
- 虚拟DOM节点:可以
父子组件传值
解决方案:自定义属性
语法结构:子组件中通过props
选项接受自定义属性
① 编辑子组件,添加自定义属性
<template><p>data中的数据:{{ msg }}</p><p>自定义属性数据:{{ dataFromParent }}</p>
</template><script>
export default {// 声明自定义属性:一个组件可能会存在多个属性,第一种格式通过数组接受数据// 定义的属性名称,用于存储接受的数据,可以和data选项中的变量一样使用;不能和data选项变量重名props: ['dataFromParent'],data() {return {msg: "xxxx"}}....
}
</script>
② 编辑父组件,通过属性传递数据
<template><!-- 父组件给子组件传递数据 --><My-child :dataFromParent="info"></My-child><My-child :data-from-parent="info"></My-child>
</template>
<script>
import MyChild from "./MyChild"
export default {data() {info: "父组件数据"}
}
</script>
③ 自定义属性扩展
自定义属性通过props
选项定义,除了基本语法定义,还可以给自定义属性添加类型约束和默认值
- 类型检查
export default {props: {dataFromParent: String}
}
- 默认数据
export default {props: {dataFromParent: String,// default: "默认值"default: function() {return "默认值"}}
}
- 数据校验
export default {props: {dataFromParent: String,default: "默认值",validator: function(value) {//执行自定义属性接受的数据的校验,可以在控制台产生校验信息return true/false}}
}
子父组件传值
解决方案:自定义事件
基本语法:通过实例选项的this.$emit()
触发自定义事件
① 子组件触发事件
<template><button @click="sendData">发送数据给父组件</button>
</template>
<script>
export default {data() {return {msg: "子组件数据"}},methods: {sendData() {// 触发自定义事件,传递数据this.$emit("childEvent", this.msg)}}
}
</script>
② 父组件监听事件
<template><!-- 标签节点上,通过监听自定义事件,接受子组件数据 --><My-child @childEvent="getData"></My-child><My-child @child-event="getData"></My-child>
</template>
<script>
import MyChild from "./MyChild"
export default {data() {return {dataFromChild: ""}},methods: {getData(value) {this.dataFromChild = value}},components: {MyChild}
}
</script>
(4) 组件之间传值
组件之间的传值,任意一个项目中的两个组件都有直接或者间接的关系,完全可以通过父子组件传值完成不同组件之间的传值,如图所示:
组件之间的传值,完全可以通过自定义事件中心,避免多个组件参与传递数据的复杂度!
① 定义事件中心
创建:src/utils/events.js
:创建一个空白的vue
实例,主要用于数据传递
/*** 自定义事件中心* events.js*/
import Vue from "vue"const vm = new Vue()export default vm
② 组件1发送数据
<template><button @click="sendData2">发送数据给其他组件</button>
</template>
<script>
// 导入事件中心
import vm from "../utils/event"export default {data() {return {msg: "子组件数据"}},methods : {sendData2() {// 事件中心 触发自定义事件vm.$emit("customEvent", this.msg)}}
}
</script>
③ 组件2接受数据
<template><p>等待接受数据:{{ }}</p>
</template>
<script>
// 导入事件中心
import vm from "../utils/event"export default {created() {// 组件加载的时候执行函数,监听自定义事件this.getData();},data() {return {dataFromOther: "等待接受"}},methods: {getData() {vm.$on("customEvent", value => {// 接受自定义事件传递的数据this.dataFromOther = value})}}
}
</script>
小总结:关于父子组件传值问题
父子组件之间的传值不涉及页面视图的切换!
一定是在单个页面组件中,已经存在父子组件嵌套关系,才是父子组件传值的操作场景!
问题:如果出现多个页面组件切换,如何完成数据传递? 解决方案:路由传参
vuex
state mutations同步函数 actions异步函数 getters
vuex
解耦合:辅助函数
(1)state
选项:
vue
组件中,需要通过耦合语法访问数据
this.$store.state.brandList
vuex
中提供了一个函数mapState
,用于将数据进行解耦合访问
<template><p>{{ $store.state.brandList }}</p><p>{{ brandList }}</p>
</template>
<script>
import {mapState} from "vuex"
export default {computed: {...mapState({brandList: state=> state.brandList})}
}
</script>
(2) actions
选项
vue
组件中,需要通过耦合语法访问函数
this.$store.dispath('getBrandListActions')
vuex
中提供了辅助函数mapActions
,通过解耦合的方式访问对应的函数操作
<template>
</template>
<script>import {mapActions} from "vuex"export default {created(){// 原始耦合this.$store.dispatch('getBrandListActions')// 解耦合this.getBrandListActions()},methods: {...mapActions(['getBrandListActions'])}}
</script>
(3) getters
选项
vue
组件中,需要通过耦合语法访问函数
this.$store.getters.函数名称
vuex
提供了解耦合语法mapGetters
提供了解耦合访问方式
<template><p>{{ $store.getters.brandCounter }}</p><p>{{ brandCounter }}</p>
</template>
<script>import {mapGetters} from "vuex"export default {computed: {...mapGetters(['brandCounter'])}}
</script>
(4)mutations
选项[了解]
vue
组件中,需要通过耦合语法访问函数
this.$store.commit('mutations函数')
vuex
提供解耦合辅助函数mapMutations
,提供解耦合访问方式
import {mapMutations} from "vuex"export default {methods: {...mapMutations(['getBrandListMutations'])},create() {// 1、原始this.$store.commit('getBrandListMutations')// 2、解耦合this.getBrandListMutations()}
}
vue3
vue2 vue3
beforeCreate setup()
created setup()
beforeMount onbeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
activated onActivated
deactivated onDeactivated
errorCaptured onErrorCaptured
import {watch computed} from 'vue'
export default{
name:'App',
setup(){watch(()=>{},()=>{})const a=computed(()=>{})
}
}
//vue3.2
<script setup>
import {watch compted} from 'vue'
watch(
()=>{},()=>{}
)
const a=computed(()=>{})
</script>
setup存在的意义,就是为了使用新增的组合API,并且这些组合API只能在setup内使用
setup调用的时机是创建组件实例然后初始化props,紧接着就是调用setup函数,从生命周期钩子的角度看,他会在beforeCreate钩子之前被调用,所以setup内拿不到this上下文
除去beforeCreate和created,在setup方法中,有9个旧的生命周期钩子,可以在setup中访问
1.setup():开始创建组件之前,在beforeCreate和created之前执行,创建的是date和method
2.onBeforeMount():组件挂载到节点之前执行的函数
3.onMounted():组件挂载完成之后执行的函数
4.onBeforeUpdate():组件更新之前执行的函数
5.onUpdated():组件更新完成之后执行额函数
6.onBeforeUnmount():组件卸载之前执行的函数
7.onUnmounted():组件卸载完成之后执行的函数
8.onActived():被keep-alive缓存的组件激活是调用
9.onDeactivated():被 keep-alive 缓存的组件停用时调用,比如从 A 组件,切换到 B 组件,A 组件消失时执行。
10.onErrorCaptured():当捕获一个来自子孙组件的异常时激活钩子函数。
组件如何接受外部传入的参数
Vue 3.2 为我们在 script 内默认添加了三个方法,这三个方法分别是**defineProps **、 **defineEmits **和 defineExpose
defineProps
我们现在需要一个父子组件的传值例子,修改上述 src/App.vue 文件如下:
<template><Test :count="count"></Test>
</template>
<script setup>
import { ref } from 'vue'
import Test from './components/Test.vue'const count = ref(0)
</script>
在 src/components 下新增 Test.vue 组件:
<template><div>{{ props.count }}</div>
</template>
<script setup>
const props = defineProps({ count: Number })
console.log('props', props)
</script>
在页面中我们打印了 props,可以发现它被 Proxy 代理过,这是 Vue3 实现响应式的核心 API,也就是说从父亲组件传到子组件的 count 变量,已经是响应式数据。
并且在子组件内,可以通过 watchEffect 和 watch 观察到数据的变化情况,我们来试试让数据在父组件变化起来,分别做如下修改:
// App.vue
<template><Test :count="count"></Test>
</template>
<script setup>
import { ref } from 'vue'
import Test from './components/Test.vue'const count = ref(0)
setTimeout(() => {count.value = 100
}, 2000)
</script>
// Test.vue
<template><div>{{ props.count }}</div>
</template>
<script setup>
import { watchEffect } from 'vue'
const props = defineProps({ count: Number })
watchEffect(() => {console.log('props.count = ', props.count)})
</script>
defineEmits
该属性的作用是在子组件获取父组件传递进来的方法,我们同样用一个例子来演示该属性的作用,在 App.vue 添加一个 add 方法如下:
<template><Test :count="count" @add="add"></Test>
</template>
<script setup>
import { ref } from 'vue'
import Test from './components/Test.vue'const count = ref(0)
const add = () => {count.value += 1
}
</script>
这里声明 add 方法,就不用再像 setup 函数那样,将其 return 出去。 在子组件内,通过 defineEmits 接受方法,如下所示:
<template><div>{{ props.count }}</div><button @click="add">+1</button>
</template>
<script setup>
const props = defineProps({ count: Number })
// 获取父组件传进来的add方法
const emit = defineEmits(['add'])const add = () => {// 执行父组件传进来的add方法emit('add')
}
</script>
使用上,和 Vue 2 的区别不大,主要区别在于如何获取上
defineExpose
在 Vue 3.2.x 版本出来前,我们使用 Vue3 开发项目都是用 setup 函数的方式,在这种方式下,父组件通过 ref 去获取子组件 return 出来的方法是比较方便的,我们修改 App.vue 和 Test.vue 组件如下:
// App.vue
<template><Test :count="count" ref="TestRef"></Test>
</template>
<script setup>
import { ref } from 'vue'
import Test from './components/Test.vue'const count = ref(1)
const TestRef = ref()
console.log('TestRef', TestRef)
</script>
// Test.vue
<template><div>{{ props.count }}</div>
</template>
<script>
export default {name: 'Test',props: {count: Number},setup(props) {const testFn = () => {console.log('我是测试方法')}return {props,testFn // 将这个测试方法 return 出去}}
}
</script>
上图显示 TestRef 的打印结果,也就意味着 App.vue 可以拿到 Test.vue 内部的方法。这个特性能帮我们实现很多有趣的组件。
但是到了 Vue 3.2.x 版本,使用 script setup 后,父组件就拿不到子组件的内部方法了,修改Test.vue 如下所示:
// Test.vue
<template><div>{{ props.count }}</div>
</template>
<script setup>
const props = defineProps({ count: Number })
const testFn = () => {console.log('这是测试方法')
}
</script>
此时,defineExpose 的作用就得以体现了。在 Test.vue 中,将想要往外抛出的方法作为参数放到 defineExpose 中,如下所示:
defineExpose({ testFn })
reactive
reactive 是 Vue 3 中提供的实现响应式数据的方法。在 Vue 2 中实现响应式数据是通过 Object 的 defineProPerty 属性来实现的,而在 Vue 3 中的响应式是通过 ES2015 的 Proxy 来实现
reactive 参数必须是对象
reactive 方法接受一个对象(json 或 Array)。
<!--App.vue-->
<template><p>{{ state.title }}</p>
</template>
<script setup>
import { reactive } from 'vue'
const state = reactive({title: 'json'
})
</script>
尝试着修改上面的 reactive 参数为:
<template><p>{{ state }}</p>
</template>
const state = reactive(['arr1', 'arr2', 'arr3'])
同学们可能会有疑问,为什么数组也可以直接渲染呢。这里你可以把数组理解为特殊的对象。我们平时常用的普通对象如下所示:
const obj = { a: ‘1’, b: ‘2’ }
数组作为特殊的对象,如下:
const arr = [‘a’, ‘b’]
此时你可以把它看作:
const arr = { 0: ‘a’, 1: ‘b’ }
所以我们同样可以使用键值对的形式获取值,如 arr[0]。所以这就解释了为什么 reactive 还可以接受数组的原因。
reactive 包裹的对象,已经通过 Proxy 进行响应式赋能,所以我们可以通过如下形式修改值,会直接体现在 template 模板上。
<template><p>{{ state.title }}</p>
</template>
<script setup>
import { reactive } from 'vue'
const state = reactive({title: '十三'
})setTimeout(() => {state.title= '十六'
}, 2000)
</script>
2 秒后你将会在浏览器上看到“十三”变成了“十六”。
响应式转换是“深层的”,会影响对象内部所有嵌套的属性。基于 ES2015 的 Proxy 实现,返回的代理对象不等于原始对象。建议仅使用代理对象而避免依赖原始对象。
上述是官方文档描述 reactive 返回的代理后的对象,内部的二级三级属性,都会被赋予响应式的能力,所以官方建议,使用 reactive 返回的值,而不要去使用原始值。
ref
ref 和 reactive 一样,同样是实现响应式数据的方法。在业务开发中,我们可以使用它来定义一些简单数据,如下所示:
<template><p>{{ count }}</p>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
修改数据,可以通过 count.value = 1 类似这样的语法去修改。但是为什么它需要这样去修改变量,而 reactive 返回的对象可以直接修改如 state.count = 1 。
原因是 Vue 3 内部将 ref 悄悄的转化为 reactive,如上述代码会被这样转换:
ref(0) => reactive({ value: 0 })
所以 count 相当于 reactive 返回的一个值,根据 reactive 修改值的方式,就可以理解为什么 ref 返回的值是通过 .value 的形式修改值了。
还有一点需要注意,当 ref 作为渲染上下文的属性返回(即在 setup() 返回的对象中)并在模板中使用时,它会自动解套,无需在模板内额外书写 .value。之所以会自动解套,是因为 template 模板在被解析的时候,Vue3 内部通过判断模板内的变量是否是 ref 类型。如果是,那就加上 .value,如果不是则为 reactive 创建的响应集代理数据。
我们不妨打印一下 ref 创建的对象 console.log(count),浏览器控制台如下图所示:
没错,就是通过上图 __v_isRef 变量去判断,模板内的变量是否为 ref 类型。判断类型也可以通过 isRef 方法,如下:
<template><p>{{ count }}</p>
</template>
<script setup>
import { ref, isRef } from 'vue'
const count = ref(0)
console.log(isRef(count)) // true
</script>
在 Vue 2 中,我们可以通过给元素添加 ref=“xxx” 属性,然后在逻辑代码中通过 this.$refs.xxx 获取到对应的元素。
到了 Vue 3 后,setup 函数内没有 this 上下文,因此我们可以通过 ref 方法去获取,并且还需要在页面挂载以后才能拿到元素。
<template><div ref='shisanRef'>十三</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const shisanRef = ref(null)
onMounted(() => {console.log(shisanRef)
})
</script>
computed
Vue 2 时代,computed 作为选项出现在页面中,而到了 Vue 3 时代,它将会以钩子函数的形式出现。我们先来修改 App.vue 下的代码,如下所示:
<template><p>{{ text }}</p>
</template>
<script setup>
import { reactive, computed } from 'vue'
const state = reactive({name: '十三',desc: '你好'
})const text = computed(() => {console.log('11')return state.name + state.desc
})setTimeout(() => {state.name = '十六'
}, 2000)
</script>
上述代码通过 computed 函数将 name 和 desc 变量拼接,返回 text 渲染在模板上。
这里要注意的是 2 秒后,name 变量将会被重新赋值,那么 computed 函数内带有 state.name,所以会被动态计算,重新返回 text 值,浏览器会有如下变化:
若是将 computed 方法内的函数做如下改动:
…
const text = computed(() => {
console.log(‘11’)
return state.desc
})
…
少了 state.name,2 秒后,你将不会看到控制台打印 11,因为函数内不会被检测执行。
上述形式 computed 返回的值是不可修改的,通过 get 和 set 的形式返回的值是可修改的,不过这种情况的使用场景不多,这里不作深究。
readonly
readonly 顾名思义,用于创建一个只读的数据,并且所有的内容都是只读,不可修改。我们看如下代码:
<template><p>{{ state.name }}</p><p>{{ state.desc }}</p><button @click="fn">修改</button>
</template>
<script setup>
import { reactive, computed, readonly } from 'vue'
const state = readonly({name: '十三',desc: '你好'
})const fn = () => {state.name = '十六'state.desc = '他好'console.log('state', state)
}
</script>
我们用 readonly 创建一个数据,将其渲染在 template 模板下,并且通过 fn 函数,修改这个数据,看看浏览器会有什么反馈。
点击按钮之后,如上图所示,控制台报警告了,并且 state 打印出来之后,内部数据也没有变化。
watchEffect
首先 watchEffect 会追踪响应式数据的变化,并且还会在第一次渲染的时候立即执行,我们来看看下面的例子:
<template><div><h1>{{ state.search }}</h1><button @click="handleSearch">改变查询字段</button></div>
</template>
<script setup>
import { reactive, watchEffect } from 'vue'
let state = reactive({search: Date.now()
})
watchEffect(() => {console.log(`监听查询字段${state.search}`)
})const handleSearch = () => {state.search = Date.now()
}
</script>
watchEffect 接受一个回调函数作为参数,并且该回调函数内如果有响应式变量,那么当我执行 handleSearch 方法改变 search 变量时,回调函数也会被执行,如下所示:
watchEffect 函数返回一个新的函数,我们可以通过执行这个函数或者当组件被卸载的时候,来停止监听行为。来看下面代码:
let timer = null
let state = reactive({search: Date.now()
})
// 返回停止函数
const stop = watchEffect((onInvalidate) => {console.log(`监听查询字段${state.search}`)
})
const handleSearch = () => {state.search = Date.now()
}
setTimeout(() => {console.log('执行 stop 停止监听')stop() // 2 秒后停止监听行为
}, 2000)
我们一直点击按钮,控制台会一直打印改变的数据。当 2 秒是 stop 方法被执行,停止监听后,控制台不再打印数据。如下图所示:
watchEffect 的回调方法内有一个很重要的方法,用于清除副作用。它接受的回调函数也接受一个函数 onInvalidate。名字不重要,重要的是它将会在 watchEffect 监听的变量改变之前被调用一次,具体执行顺序我们通过代码来解释:
<template><div><h1>{{ state.search }}</h1><button @click="handleSearch">改变查询字段</button></div>
</template>
<script setup>
import { reactive, watchEffect } from 'vue'
let state = reactive({search: Date.now()
})
const stop = watchEffect((onInvalidate) => {console.log(`监听查询字段${state.search}`)onInvalidate(() => {console.log('执行 onInvalidate')})
})const handleSearch = () => {state.search = Date.now()
}
</script>
每当我点击按钮,改变 search 值时,onInvalidate 会在监听打印之前被执行一次。
那么要它何用呢?用处非常大。举个例子,我们需要监听 search 的变化,去请求接口数据,此时接口是异步返回的,每当我改变 search 都会去请求一次接口,那么有可能 search 改变的很频繁,那就会频繁的去请求接口,导致服务端压力倍增。我们可以通过这个特性去降低服务端的压力,具体逻辑如下:
<template><div><h1>{{ state.search }}</h1><button @click="handleSearch">改变查询字段</button></div>
</template>
<script setup>
import { reactive, watchEffect } from 'vue'
let timer = null
let state = reactive({search: Date.now()
})
watchEffect((onInvalidate) => {console.log(`监听查询字段${state.search}`)timer = setTimeout(() => {console.log('模拟接口异步请求,3 秒之后返回详情信息')}, 3000)onInvalidate(() => {console.log('清除');clearInterval(timer);})
})const handleSearch = () => {state.search = Date.now()
}
</script>
在 watchEffect 回调函数内,我用 setTimeout 的形式去模拟响应时间为 3 秒的异步请求,上面代码可以理解为 3 秒之内如果你不去改变 search 变量,那么页面就成功返回接口数据,如果在 3 秒之内你再次点击按钮改变了 search 变量,onInvalidate 将会被触发,从而清理掉上一次的接口请求,然后根据新的 search 变量去执行新的请求。我们来看浏览器的表现:
watch
watch 的功能和之前的 Vue 2 的 watch 是一样的。和 watchEffect 相比较,区别在 watch 必须指定一个特定的变量,并且不会默认执行回调函数,而是等到监听的变量改变了,才会执行。并且你可以拿到改变前和改变后的值,代码如下:
<template><div><h1>{{ state.search }}</h1><button @click="handleSearch">改变查询字段</button></div>
</template>
<script setup>
import { reactive, watch } from 'vue'
let timer = null
let state = reactive({search: Date.now()
})
watch(() => {return state.search
}, (nextData, preData) => {console.log('preData', preData)console.log('nextData', nextData)
})const handleSearch = () => {state.search = Date.now()
}
</script>
提供/注入(provide/inject)
祖先 想要传递数据给 儿子 的的话,正常情况下,需要先传递给 父亲 组件,然后 父亲 组件再将数据传给 儿子 组件。
现在我们有了 provide/inject,便可以在 祖先组件 声明 provide,然后在 儿子组件 通过 inject 拿到数据。下面我们用代码来诠释上面的分析。
Vue 2写法
清空上述 App.vue 的代码,将其作为 祖先组件,代码如下:
<template><div><h1>提供/注入</h1><Father /></div>
</template>
<script>
import Father from './components/Father.vue'export default {components: {Father},provide: {name: '陈尼克'}
}
</script>
在 src/components 文件夹新建两个文件 Father.vue 和 Son.vue 如下:
<!--Father.vue-->
<template><div>我是父亲</div><Son />
</template>
<script>
import Son from './Son.vue'
export default {name: 'Father',components: {Son}
}
</script>
<!--Son.vue-->
<template><div>我是儿子,{{ name }}</div>
</template>
<script>
export default {name: 'Son',inject: ['name']
}
</script>
Vue 3 写法
之前说过 Vue 3 作出最大的改动就是将 options 的书写形式改成了 hooks 的钩子函数形式。privide/inject 也不例外,我们使用它们需要通过 vue 去解构出来,下面我们修改上述代码如下:
<!--App.vue-->
<template><div><h1>提供/注入</h1><Father /></div>
</template>
<script setup>
import { provide } from 'vue'
import Father from './components/Father.vue'provide('name', '陈尼克') // 单个声明形式
provide('info', {work: '前端开发',age: '18'
}) // 多个声明形式
</script>
<!--Son.vue-->
<template><div>我是儿子,{{ name }}</div><div>职业:{{ info.work }}</div><div>年龄:{{ info.age }}</div>
</template>
<script setup>
import { inject } from 'vue'
const name = inject('name', '嘻嘻') // 第二个参数为默认值,可选
const info = inject('info')
</script>
当我们需要修改传入的数据时,Vue 不建议我们直接在接收数据的页面修改数据源,用上述的例子就是不建议在 Son.vue 组件内去修改数据源,我们可以在 App.vue 组件内通过 provide 传递一个修改数据的方法给 Son.vue,通过在 Son.vue 内调用该方法去改变值。我们将代码做如下修改:
<!--App.vue-->
<template><div><h1>提供/注入</h1><Father /></div>
</template>
<script setup>
import { provide, ref } from 'vue'
import Father from './components/Father.vue'const name = ref('陈尼克')
provide('name', name) // 单个声明形式
provide('info', {work: '前端开发',age: '18'
}) // 多个声明形式const changeName = () => {name.value = '李尼克'
}provide('changeName', changeName)
</script>
<!--Son.vue-->
<template><div>我是儿子,{{ name }}</div><div>职业:{{ info.work }}</div><div>年龄:{{ info.age }}</div><button @click="changeName">修改名字</button>
</template>
<script setup>
import { inject } from 'vue'
const name = inject('name', '嘻嘻') // 第二个参数为默认值,可选
const info = inject('info')
const changeName = inject('changeName')
</script>
这里解释一下,在 Son.vue 组件中,你可以直接修改 inject 传进来的 name 值。但是你细想,数据源存在于 App.vue 中,你在 Son.vue 中私自修改了数据源传进来的值,那两边的值就会产生紊乱,上述业务逻辑属于简单的,当你在公司正式项目中这样做的时候,数据源就会变得杂乱无章,页面组件变得难以维护。综上所述,一定要控制好数据源,保持单一数据流。
vue3性能和业务层面上的提升
虚拟 DOM 性能优化
PatchFlag(静态标记)
Vue 2 中的虚拟 DOM 是全量对比的模式,而到了 Vue 3 开始,新增了静态标记(PatchFlag)**。在更新前的节点进行对比的时候,只会去对比带有静态标记的节点。**并且 PatchFlag 枚举定义了十几种类型,用以更精确的定位需要对比节点的类型。下面我们通过图文实例分析这个对比的过程。假设我们有下面一段代码:
老八食堂
{{ message }}
通过上图,我们发现,Vue 2 的 diff 算法将每个标签都比较了一次,最后发现带有 {{ message }} 变量的标签是需要被更新的标签,显然这还有优化的空间。
在 Vue 3 中,对 diff 算法进行了优化,在创建虚拟 DOM 时,根据 DOM 内容是否会发生变化,而给予相对应类型的静态标记(PatchFlag)
观察上图,不难发现视图的更新只对带有 flag 标记的标签进行了对比(diff),所以只进行了 1 次比较,而相同情况下,Vue 2 则进行了 3 次比较。这便是 Vue 3 比 Vue 2 性能好的第一个原因。
我们再通过把模板代码转译成虚拟 DOM,来验证我们上述的分析是否正确。我们可以打开模板转化网站,对上述代码进行转译:
上图蓝色框内为转译后的虚拟 DOM 节点,第一个 p 标签为写死的静态文字,而第二个 p 标签则为绑定的变量,所以打上了 1 标签,代表的是 TEXT(文字),标记枚举类型如下:
诸如上述代码,如果将 PAGE_SIZE = 10 写在 getData 方法内,每次调用 getData 都会重新定义一次变量。
Vue 3 在这方面也做了同样的优化,继续用我们上一个例子写的代码,观察编译之后的虚拟 DOM 结构,如下所示。
export const enum PatchFlags {TEXT = 1,// 动态的文本节点CLASS = 1 << 1, // 2 动态的 classSTYLE = 1 << 2, // 4 动态的 stylePROPS = 1 << 3, // 8 动态属性,不包括类名和样式FULL_PROPS = 1 << 4, // 16 动态 key,当 key 变化时需要完整的 diff 算法做比较HYDRATE_EVENTS = 1 << 5, // 32 表示带有事件监听器的节点STABLE_FRAGMENT = 1 << 6, // 64 一个不会改变子节点顺序的 FragmentKEYED_FRAGMENT = 1 << 7, // 128 带有 key 属性的 FragmentUNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有 key 的 FragmentNEED_PATCH = 1 << 9, // 512DYNAMIC_SLOTS = 1 << 10, // 动态 soltHOISTED = -1, // 特殊标志是负整数表示永远不会用作 diffBAIL = -2 // 一个特殊的标志,指代差异算法
}
hoistStatic(静态提升)
我们平时在开发过程中写函数的时候,定义一些写死的变量时,都会将变量提升出去定义,如下所示:
const PAGE_SIZE = 10
function getData () {$.get('/data', {data: {page: PAGE_SIZE},...})
}
没有做静态提升前:
hoist.png
选择 Option 下的 hoistStatic:
optionh.png
静态提升后:
hoist2.png
细心的同学会发现, 老八食堂 被提到了 render 函数外,每次渲染的时候只要取 _hoisted_1 变量便可。认真看文章的同学又会发现一个细节, _hoisted_1 被打上了 PatchFlag ,静态标记值为 -1 ,特殊标志是负整数表示永远不会用作 diff。也就是说被打上 -1 标记的,将不在参与 diff 算法,这又提升了 Vue 的性能。
cacheHandler(事件监听缓存)
默认情况下 @click 事件被认为是动态变量,所以每次更新视图的时候都会追踪它的变化。但是正常情况下,我们的 @click 事件在视图渲染前和渲染后,都是同一个事件,基本上不需要去追踪它的变化,所以 Vue 3 对此作出了相应的优化叫 事件监听缓存,我们在上述代码中加一段:
giao.png
在未开启 事件监听缓存 的情况下,我们看到这串代码编译后被静态标记为 8,之前讲解过被静态标记的标签就会被拉去做比较,而静态标记 8 对应的是“动态属性,不包括类名和样式”。 @click 被认为是动态属性,所以我们需要开启 Options 下的 cacheHandler 属性,如下图所示:
giao1.png
细心的同学又会发现,开启 cacheHandler 之后,编译后的代码已经没有静态标记(PatchFlag),也就表明图中 p 标签不再被追踪比较变化,进而提升了 Vue 的性能。
SSR 服务端渲染
当你在开发中使用 SSR 开发时,Vue 3 会将静态标签直接转化为文本,相比 React 先将 jsx 转化为虚拟 DOM,再将虚拟 DOM 转化为 HTML,Vue 3 已经赢了。
ssr.jpg
StaticNode(静态节点)
上述 SSR 服务端渲染,会将静态标签直接转化为文本。在客户端渲染的时候,只要标签嵌套得足够多,编译时也会将其转化为 HTML 字符串,如下图所示:
staticnode.jpg
需要开启 Options 下的 hoistStatic
Tree-shaking
说到 Tree-shaking 这个属于,官方的解释用大白话说就是,没有被应用到的代码,编译后自动将其剔除。
我个人是这么记住 Tree-shaking 的,翻译成中文就是 摇树,树上有枯叶和绿叶,我摇动树干,枯叶掉了,新叶留着。这里的枯叶就是指没用到的代码,新叶指被应用到的代码,这么记就完全可以技术这个技术点。
在 Vue 2 中,无论有没有用到全部的功能,这些功能的代码都会被打包到生产环境中。究其原因,主要还是怪 Vue 2 生成实例是单例,这样打包的时候就无法检测到实例中的各个方法是否被引用到。如下:
import Vue from ‘vue’
Vue.nextTick(() => {})
而 Vue 3 经过改良之后,引入了 Tree-shaking 的特性,所有的方法通过模块化导入的形式。如下:
import { nextTick, onMounted } from ‘vue’
nextTick(() => {})
nextTick 方法会被打进生产包,而 onMounted 在代码里没有用到,最终不会出现在编译后的代码里。
Tree-shaking 做了两件事:
编译阶段利用 ES 的模块化判断有哪些模块已经加载。
判断哪些模块和变量,没有被使用或者引用到,从而删除对应的代码。
光看文字没有说服力,我们通过代码实例来演示一遍,通过 Vue CLI 启动一个 Vue 2 的项目,修改 App.vue 如下所示:
我们再加一个 Option,如下所示:
业务代码从 2.04 -> 2.10,而工具包还是 89.66。由此可见,增减代码,并不会影响工具包的打包大小。
我们再来看看 Vue 3 的表现,通过 Vue CLI 启动一个 Vue 3 的项目,App.vue 作如下修改:
我们加一个添加一个 computed 方法,如下所示:
<template><div>{{ state.test }}</div>
</template>
<script>
import { reactive, computed } from 'vue'
const state = reactive({test: '十三'
})
const testc = computed(() => {return state.test + '测试'
})
</script>
添加了 computed 之后,可以看到,工具包的大小从 87.35 -> 87.39,变大了。也就是说,之前没有用到 computed,它是不会被打包到生产环境的工具包里的。
综上所述,Vue 3 的 Tree-shaking 为我们带来了一下几个升级:
减少打包后的静态资源体积
程序执行更快
相关文章:

vue2-vue3面试
v-text/v-html/v-once/v-show/v-if/v-for/v-bind/v-on beforeCreate() 已有DOM节点:可以data选项:不可以虚拟DOM节点:不可以 created():掌握 已有DOM节点:可以data选项:可以虚拟DOM节点:不可以 beforeMount…...

jmeter生成随机数的详细步骤及使用方式
Apache JMeter 是一个用于测试性能的开源工具,它可以模拟多种类型的负载并测量应用程序的性能。在 JMeter 中生成随机数可以通过使用预定义的函数来实现。以下是生成随机数的详细步骤及使用方式: 安装 JMeter: 首先,你需要在你的计…...

速盾:为什么会出现高防cdn?它适合哪些行业?
随着互联网的不断发展和普及,网络安全问题也变得日益突出。由于互联网的特性,许多企业和组织的在线业务往往面临来自网络攻击的威胁,如DDoS攻击、恶意爬虫等。为了保护在线业务的正常运行和用户数据的安全,高防CDN应运而生。 高防…...

GB∕T 25058-2019 信息安全技术 网络安全等级保护实施指南
GB∕T 25058-2019 信息安全技术 网络安全等级保护实施指南...

使用Nodejs + express连接数据库mongoose
文章目录 先创建一个js文档安装 MongoDB 驱动程序:引入 MongoDB 模块:设置数据库连接:新建一个表试试执行数据库操作:关闭数据库连接: 前面需要准备的内容可看前面的文章: Express框架搭建项目 node.js 简单…...

朗致集团面试-Java架构师
总结 三轮面试,第一轮是逻辑测试性格测试,第二轮是技术面试(面试官-刘老师),第三轮是CTO面试(面试官-屠老师)。如果第三轮面试通过,考官会问你薪资意向,如果满意的话HR就…...

Ubuntu 23.10 搜狗拼音输入法闪屏解决
问题与解决 Ubuntu 23.10下安装搜狗拼音输入法并且使用搜狗输入法时,会闪屏。站内有人说可以换使用Xorg作为桌面服务,然后使用基于X11的桌面,其实可以不用那么麻烦,只需要设置QT的环境变量QT_QPA_PLATFORMxcb,然后重新…...

备战蓝桥杯---刷杂题2
显然我们直接看前一半,然后我们按照斜行看,我们发现斜行是递增的,而同一行从左向右也是递增的,因此我们可以直接二分,同时我们发现对称轴的数为Ck,2k. 我们从16斜行枚举即可 #include<bits/stdc.h> using name…...

.[[backup@waifu.club]].svh勒索病毒数据怎么处理|数据解密恢复
尊敬的读者: 近年来,随着信息技术的迅猛发展,网络安全问题日益凸显,其中勒索病毒成为了一大威胁。.[[backupwaifu.club]].svh、.[[MyFilewaifu.club]].svh勒索病毒就是其中之一,它以其独特的传播方式和恶劣的加密手段…...

SpringFramework实战指南(八)
SpringFramework实战指南(八) 5.1 场景设定和问题复现5.2 解决技术代理模式5.1 场景设定和问题复现 准备AOP项目 项目名:spring-aop-annotation pom.xml <dependencies><!--spring context依赖--><!--当你引入Spring Context依赖之后,表示将Spring的基础依赖…...

Ceph学习 -4.Ceph组件介绍
文章目录 1.Ceph组件介绍1.1 组件介绍1.2 流程解读1.2.1 综合效果图1.2.2 数据存储逻辑 1.3 小结 1.Ceph组件介绍 学习目标:这一节,我们从组件介绍、流程解读、小结三个方面来学习。 1.1 组件介绍 无论是想向云平台提供 Ceph 对象存储和 Ceph 块设备服务…...

Python100个库分享第13个—awesome-slugify(处理Unicode)
目录 专栏导读库的介绍库的安装基础用法1:用‘-’连接基础用法1:汉字转拼音用‘-’连接有个类似的库 —python-slugify安装总结 专栏导读 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手 🏳️…...

01 SQL基础 -- 初识数据库与安装
一、初识数据库 数据库是将大量数据保存起来,通过计算机加工而成的可以进行高效访问的数据集合。该数据集合称为数据库(Database, DB)。用来管理数据库的计算机系统称为数据库管理系统(Database Management System, DBMS) 1.1 DBMS 的种类 DBMS 主要通过数据的保存格式…...

PyTorch搭建Autoformer实现长序列时间序列预测
目录 I. 前言II. AutoformerIII. 代码3.1 Encoder输入3.1.1 Token Embedding3.1.2 Temporal Embedding 3.2 Decoder输入3.3 Encoder与Decoder3.3.1 初始化3.3.2 Encoder3.3.3 Decoder IV. 实验 I. 前言 前面已经写了很多关于时间序列预测的文章: 深入理解PyTorch中…...

FFmpeg: 简易ijkplayer播放器实现--06封装打开和关闭stream
文章目录 流程图stream openstream close 流程图 stream open 初始化SDL以允许⾳频输出;初始化帧Frame队列初始化包Packet队列初始化时钟Clock初始化音量创建解复用读取线程read_thread创建视频刷新线程video_refresh_thread int FFPlayer::stream_open(const cha…...

使用Android完成案例教学
目录 题目:完成在Android平台下2个玩家分别利用2个手机连接在同一局域网下通过滑动摇杆分别使红飞机和黄飞机移动的开发。(全代码解析) 题目:完成在Android平台下2个玩家分别利用2个手机连接在同一局域网下通过滑动摇杆分别使红飞…...

面向对象设计原则实验“依赖倒置原则”
高层模块不应该依赖于低层模块。二者都应该依赖于抽象。抽象不应该依赖于细节。细节应该依赖于抽象。 (开闭原则、里氏代换原则和依赖倒转原则的三个实例很相似,原因是它之间的关系很紧密,在实现很多重构时通常需要同时使用这三个原则。开闭…...

PMP考试到底难在哪里?
虽然PMP考试整体的并没有那么难,通过率也比较高,但PMP考试设计地非常巧妙,所以在面对考试时也不能掉以轻心。 01涉及面广 目前PMP考试内容大部分来源于教材《PMBOK指南》和《敏捷实践指南》。 作为考试出题的知识基础《PMBOK指南》&#x…...

Linux执行命令监控详细实现原理和使用教程,以及相关工具的使用
Linux执行命令监控详细实现原理和使用教程,以及相关工具的使用。 0x00 背景介绍 Linux上的HIDS需要实时对执行的命令进行监控,分析异常或入侵行为,有助于安全事件的发现和预防。为了获取执行命令,大致有如下方法: 遍…...

算法设计与分析实验报告c++实现(生命游戏、带锁的门、三壶谜题、串匹配问题、交替放置的碟子)
一、实验目的 1.加深学生对分治法算法设计方法的基本思想、基本步骤、基本方法的理解与掌握; 2.提高学生利用课堂所学知识解决实际问题的能力; 3.提高学生综合应用所学知识解决实际问题的能力。 二、实验任务 1、 编…...

【电子通识】热风枪的结构与使用方法
热风枪的结构 热风枪是专门用来拆焊、焊接贴片元器件和贴片集成电路的焊接工具,它主要由主机和热风焊枪两大部分构成。 热风枪主要有电源开关、风速设置、温度设置、热风连接等部件组成。根据不同品牌和价位的热风枪,有一些功能齐全的也集成了烙铁功能。…...

mysql知识点
MySQL 中有哪几种锁 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小&…...

css Animation 动画-右进左出
transform: rotate(旋转) | scale(缩放) | skew(倾斜) | translate(移动) ;<style> .jinggao {width: 60vw;display: inline-block;text-align: center;overflow: hidden;box-…...

第十三届蓝桥杯省赛大学B组填空题(c++)
A.扫雷 暴力模拟AC: #include<iostream> using namespace std; const int N105; int n,m,map[N][N],ans[N][N]; int dx[8]{-1,-1,0,1,1,1,0,-1}; int dy[8]{0,1,1,1,0,-1,-1,-1}; int count(int x,int y){int cnt0;for(int i0;i<8;i){int xxxdx[i];int yyydy[i];if(…...

天星金融(原小米金融)深耕金融知识领域,助力消费者提升金融素养
近年来,依托生活和消费品质不断提升的时代契机,信用卡持卡人的数量以及信用卡消费的频率不断增加,信用卡还款问题也日益凸显。部分不法分子打着“智能还款”、“精养提额”的口号“踏浪”入场,实则行诱导、诈骗之实。天星金融&…...

中国手机频段介绍
中国目前有三大运营商,分别是中国移动、中国联通、中国电信,还有一个潜在的运营商中国广电,各家使用的2/3/4G的制式略有不同 中国移动的GSM包括900M和1800M两个频段。 中国移动的4G的TD-LTE包括B34、B38、B39、B40、B41几个频段,…...

企业如何使用SNP Glue将SAP与Snowflake集成?
SNP Glue是SNP的集成技术,适用于任何云平台。它最初是围绕SAP和Hadoop构建的,现在已经发展为一个集成平台,虽然它仍然非常专注SAP,但可以将几乎任何数据源与任何数据目标集成。 我们客户非常感兴趣的数据目标之一是Snowflake。Sno…...

算法设计与分析实验报告c++实现(最近点对问题、循环赛日程安排问题、排序问题、棋盘覆盖问题)
一、实验目的 1.加深学生对分治法算法设计方法的基本思想、基本步骤、基本方法的理解与掌握; 2.提高学生利用课堂所学知识解决实际问题的能力; 3.提高学生综合应用所学知识解决实际问题的能力。 二、实验任务 1、最…...

Vue - 你知道Vue中computed和watch的区别吗
难度级别:中高级及以上 提问概率:70% 二者都是用来监听数据变化的,而且在日常工作中大部分时候都只是局限于简单实用,所以到了面试中很难全面说出二者的区别。接下来我们看一下,二者究竟有哪些区别呢? 先说computed,它的主要用途是监听…...
POJ2976 Dropping tests——P4377 [USACO18OPEN] Talent Show G 【分数规划二分法+贪心/背包】
POJ2976 Dropping tests 【分数规划二分法+贪心】 有 n 个物品,每个物品有两个权值 a 和b。你可以放弃 k 个物品,选 n-k 个物品,使得最大。 输入多个样例,第一行输入n 和 k,第二行输入n 个 ai ,第三行输入 n 个 bi,输入 0 0 结束。 输出答案乘100 后四舍五入到整数…...