异步组件
在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue 提供了 defineAsyncComponent 方法来实现此功能。
同步组件
从当前文件定义的
异步组件
组件由异步获取,可能是从网络或是客户端获取,不是当前文件定义的
Suspense 内置组件
配合异步组件使用,在异步组件加载完成前显示自定义内容
<div id="app">
<com-a></com-a>
<Suspense>
<my-com></my-com>
<template #fallback>
加载中...
</template>
</Suspense>
</div>
const {defineAsyncComponent } = Vue
import {defineAsyncComponent} from 'Vue'
// 定义异步组件
const MyCom = defineAsyncComponent(()=>{
return new Promise((resolve,reject)=>{
// 从服务端获取异步组件ComB
setTimeout(()=>{
resolve(ComB)
},3000)
})
})
//定义组件 服务端返回的异步组件
const ComB = {
template:` <p>异步组件</p>`
}
自定义指令
内置指令
v-开头的特殊属性
<input v-model="message" >
v-show v-bind v-on v-for v-if
自定义指令
全局自定义指令
app.directive()
<div id="app">
<p v-color="'red'">元素1</p> <!--这里'red'用字符串,如果不用引号red就在下面根data()处设置-->
</div>
<script>
const {createApp} =Vue
app.directives('color',{//全局指令
mounted(el,binding){ //binding的值是'red'
el.style.color = binding.value
}
})
const app = createApp({
data(){
return {
red:'blue' //如果上面的red没引号 就是将这里的blue传过去
}
},
}).mount('#app')
</script>
局部自定义指令
directives
<div id="app">
<p v-color="'red'">元素1</p> <!--这里'red'用字符串,如果不用引号red就在下面根data()处设置-->
</div>
<script>
const {createApp} =Vue
const app = createApp({
//局部指令
directives:{
//指令名称
color:{
//el 使用指令的元素节点
mounted(el,binding){//binding的值是'red'
el.style.color = binding.value
}
},
},
data(){
return {
red:'blue' //如果上面的red没引号 就是将这里的blue传过去
}
},
}).mount('#app')
</script>
自定义插件
插件
为vue提供全局功能的技术
vueRouter(实现路由) vuex(实现状态管理)
定义
包含install方法的一个对象
const aplugin = { //插件名名
install(app,options){ //app是整个应用实例 //options选项 注册插件时传入的可以是对象函数
//定义全局功能
// 例如全局指令:全局组件 全局指令
// 例如全局属性方法 $emit()
}
}
注册
app.use(aplugin,{/*options*/}) //aplugin是插件名
事例
<div id="app">
<p v-color="red">元素一</p>
<button-counter></button-counter>
</div>
<script>
const { createApp } = Vue
//定义插件
const myPlugin = {
install(app, options) {
app.directive('color',{
mounted(el,binding) {
//接收颜色值,设置给节点对象
let color = binding.value //颜色值
console.log('color ',color);
el.style.color = color //设置给节点对象
},
})
const app = createApp({
data() {
return {
red:'red'
}
}
})
// 注册插件
app.use(myPlugin,{
name:'插件'
})
app.mount('#app')
</script>
Teleport传送门
是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。常见例子:全屏的模态框
<Teleport>
接收一个 to
prop 来指定传送的目标。to
的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段传送到 body
标签下”
<div id="app">
<button @click="onOpen">打开模态框</button>
<teleport to="body">
<modal v-if="isShow" @close-modal="onClose">
<template #content>
<div>这是模态框内容</div>
</template>
</modal>
</teleport>
</div>
过滤器 filter(V2x了解)、混入mixins
全局过滤器
Vue.filter("过滤器名称", (形参=管道符前面的数据,形参=想要传入的数据...)=>{
//业务
return 结果
})
局部过滤器
filter:{
data(){},
methods:{},
filter:{
'过滤器名称'(参数){
return 结果
}
}
}
使用
{{ 参数 | 过滤器名称 }}
混入mixins(vue3有更佳的组合式api)
模块混入,组合成一个
<div id="app">
<mixin-com></mixin-com>
<child></child>
</div>
const { createApp } = Vue
const MixinCom = {
template:'<button @click="add">{{count}}加一</button>',
data() {
return {
count: 10,
};
},
methods: {
add(){
this.count++
}
},
}
const Child = {
mixins:[MixinCom], //混入组件 混入后加减操作的是同一个值 方法也是一个道理
template:`<div>
<button @click="minus">{{count}} 减一</button>
<button @click="add">{{count}} 加一</button>
</div>`,
methods:{
minus(){
this.count--
}
}
}
开发模式框架[面试理论]
在长期开发过程中总结出来的一种开发框架,也就是代码架构的写法
MVC
jsp + javabean + servlet
视图 模型 控制器
Model 数据模型
负责提供数据,用于数据存储及业务逻辑,在后端里充当连接数据库,获取数据的作用
View 视图模型
负责界面显示,用于与用户实现交互的页面
Contorller 控制器
product(接收请求) -> model product(让模型数据和视图进行渲染)
view productView(渲染后响应给用户)
MVP
Model 数据模型
负责提供数据,用于数据存储及业务逻辑,在后端里充当连接数据库,获取数据的作用
View 视图模型
负责界面显示,用于与用户实现交互的页面
Present 表示层
用于连接M层、V层,完成M与V的交互,还可以进行业务逻辑处理。这里M的数据不会影响到V层。
MVC和MVP的区别
用户请求在MVC里直接到的Contorller 控制器层,MVP到的View层
MVP的Present层将Model和View层进行隔离,相互不通讯,用Present层进行交互
MVVM
Model 数据模型
负责提供数据,用于数据存储及业务逻辑,在后端里充当连接数据库,获取数据的作用
View 视图模型
负责界面显示,用于与用户实现交互的页面
VM -viewModel
将Model和View进行关联的纽带
Model 数据 <- VM -viewModel -> View 视图
Model 数据 变化可以通过VM让View自动更新,View变化同理
双向绑定原理/响应式原理[面试]
采用“数据劫持”结合“发布者-订阅者”模式的方式,
通过“Object.defineProperty()”方法来劫持各个属性的setter,getter,
在数据变动时发布消息给订阅者,触发相应的监听回调。
Vue3 Proxy代理
Vue2 Object.defindPropery() 数据劫持
虚拟DOM/重排重绘[面试]
虚拟DOM
h函数是创建虚拟dom的函数,虚拟dom本质上就是一个普通的JS对象,用于描述视图的界面结构,将真实DOM节点用js对象构造出来,即模拟真实的DOM生成的一个js对象。
在vue中,每个组件都有一个render函数,每个render函数都会返回一个虚拟dom树,这也就意味着每个组件都对应一棵虚拟DOM树。
为什么需要虚拟DOM?
vue在渲染时,使用虚拟dom来替代真实dom,主要为解决渲染效率的问题
在vue中,渲染视图会调用render函数,这种渲染不仅发生在组件创建时,同时发生在视图依赖的数据更新时。如果在渲染时,直接使用真实DOM,由于真实DOM的创建、更新、插入等操作会引起重排(回流)、重绘带来大量的性能损耗,从而就会极大的降低渲染效率。
重排重绘
重绘
CSS 样式改变(例如:visibility,背景色的改变),使浏览器需要根据新的属性进行绘制。
重排
对DOM的修改引发了DOM几何元素的变化,如宽高变化等,渲染树需要重新计算,重新生成布局,重新排列元素。
重绘不一定导致重排,但重排一定会导致重绘:
单单改变元素的外观,不会引起网页重新生成布局,但当浏览器完成重排之后,将会重新绘制受到此次重排影响的部分。比如改变元素高度,这个元素乃至周边dom都需要重新绘制。
浏览器渲染引擎工作流程
浏览器渲染引擎工作时,会先解析HTML然后生成DOM树,与此同时,渲染引擎也会用CSS解析器解析CSS文档构建CSSOM树。
接下来,DOM树和CSSOM树关联起来构成渲染树(RenderTree)然后浏览器按照渲染树进行布局(Layout),最后一步通过绘制显示出整个页面。
vue 赋值异步问题/nextTikct
vue框架监听响应数据变化, 更新界面是一个异步过程
赋值异步--赋值不会异步,异步是指赋值和页面的渲染是异步
如果不是渲染异步,那么页面上会执行3次渲染,但页面上是同时更新渲染,原因是:
为了提升页面渲染的性能,对赋值做了一个合并渲染处理--它使用的原理是防抖的原理
当赋值以后会起一个定时器,如果在定时器时间内有执行第二次赋值,那么会清除第一次定时器,重新起一个新的定时器, 依此类推,到最后一次赋值,然后再执行渲染
即防抖执行最后一次,节流执行第一次
解决赋值异步问题?
console.log('当前节点宽度:', this.$refs.node.clientWidth)
可以使用异步来实现问题解决
定时器--setTimeout、setInterval、Promise、nextTick、async、requestAnimationFrame
以上的解决方案渲染出结果优先级为:Promise、nextTick、requestAnimationFrame、setTimeout、setInterval
组件的 $nextTick(cb) 方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。
$nextTick返回的是promise
通俗的理解是:等组件的DOM 更新完成之后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素。
//回调写法
this.$nextTick(() => {
console.log('nextTick 当前节点宽度:', this.$refs.node.clientWidth)
})
// Promise写法
this.$nextTick().then(() => {
console.log('nextTick 当前节点宽度:', this.$refs.node.clientWidth)
})
<template>
<div>
<h4>vue 赋值异步问题</h4>
<p> 第一个值:{{value1}}
<span class="box" ref='node' :style="{width: value1 + 'px'}"></span>
</p>
<p> 第二个值:{{value2}}</p>
<p>第三个值:{{value3}} </p>
<p><button @click="changeValue">change value</button>/p>
</div>
</template>
<script>
export default {
data() {
return {
value1: 10,
value2: 1,
value3: 2
}
},
methods: {
async changeValue() {
setTimeout(() => {
console.log('setTimeout 当前节点宽度:', this.$refs.node.clientWidth)
})
this.value1 += 1
this.value2 += 1
this.value3 += 1
Promise.resolve().then(() => {
console.log('Promise 当前节点宽度:', this.$refs.node.clientWidth)
})
let timer = setInterval(() => {
clearInterval(timer)
console.log('setInterval 当前节点宽度:', this.$refs.node.clientWidth)
})
requestAnimationFrame(() => {
console.log('requestAnimationFrame 当前节点宽度:', this.$refs.node.clientWidth)
})
this.getNodeWidth()
// 因为这里只有resolve返回,所以可以使用async+await来实现
await this.$nextTick()
console.log('nextTick 当前节点宽度:', this.$refs.node.clientWidth)
},
async getNodeWidth() {
console.log('async 当前节点宽度:', this.$refs.node.clientWidth)
}
},
updated() {
console.log('------------------- updated')
}
}
<div id="app">
<p ref="pref">{{count}}</p>
<button @click="plus">加一</button>
</div>
<script>
const { createApp } = Vue
createApp({
data() {
return {
count: 0,
}
},
methods: {
plus() {
this.count++
console.log('this.count : ', this.count)//2
// vue框架监听响应数据变化, 更新界面是一个异步过程
// 通过节点获取内容
const pEle = document.querySelector('p')
const pEle = this.$refs.pref
console.log('pEle.innerHTML :',pEle.innerHTML);//1
this.$nextTick(() => {
const pEle = this.$refs.pref
console.log('pEle.innerHTML :', pEle.innerHTML)//2
})
},
},
}).mount('#app')
</script>