vuex 是什么?怎么应用?哪种性能场景应用它?
Vuex
是一个专为Vue.js
利用程序开发的状态管理模式。vuex
就是一个仓库,仓库里放了很多对象。其中state
就是数据源寄存地,对应于个别 vue 对象外面的data
外面寄存的数据是响应式的,vue
组件从store
读取数据,若是store
中的数据产生扭转,依赖这相数据的组件也会产生更新它通过mapState
把全局的state
和getters
映射到以后组件的computed
计算属性
vuex
个别用于中大型web
单页利用中对利用的状态进行治理,对于一些组件间关系较为简单的小型利用,应用vuex
的必要性不是很大,因为齐全能够用组件prop
属性或者事件来实现父子组件之间的通信,vuex
更多地用于解决跨组件通信以及作为数据中心集中式存储数据。- 应用
Vuex
解决非父子组件之间通信问题vuex
是通过将state
作为数据中心、各个组件共享state
实现跨组件通信的,此时的数据齐全独立于组件,因而将组件间共享的数据置于State
中能无效解决多层级组件嵌套的跨组件通信问题
vuex
的State
在单页利用的开发中自身具备一个“数据库”的作用,能够将组件中用到的数据存储在State
中,并在Action
中封装数据读写的逻辑。这时候存在一个问题,个别什么样的数据会放在State
中呢?目前次要有两种数据会应用vuex
进行治理:
- 组件之间全局共享的数据
- 通过后端异步申请的数据
包含以下几个模块
state
:Vuex
应用繁多状态树, 即每个利用将仅仅蕴含一个store
实例。外面寄存的数据是响应式的,vue
组件从store
读取数据,若是store
中的数据产生扭转,依赖这相数据的组件也会产生更新。它通过mapState
把全局的state
和getters
映射到以后组件的computed
计算属性mutations
:更改Vuex
的store
中的状态的惟一办法是提交mutation
getters
:getter
能够对state
进行计算操作,它就是store
的计算属性尽管在组件内也能够做计算属性,然而getters
能够在多给件之间复用如果一个状态只在一个组件内应用,是能够不必getters
action
:action
相似于muation
, 不同在于:action
提交的是mutation
, 而不是间接变更状态action
能够蕴含任意异步操作modules
:面对简单的应用程序,当治理的状态比拟多时;咱们须要将vuex
的store
对象宰割成模块(modules
)
modules
:我的项目特地简单的时候,能够让每一个模块领有本人的state
、mutation
、action
、getters
,使得构造十分清晰,方便管理
答复范例
思路
- 给定义
- 必要性论述
- 何时应用
- 拓展:一些集体思考、实践经验等
答复范例
Vuex
是一个专为Vue.js
利用开发的 状态管理模式 + 库。它采纳集中式存储,治理利用的所有组件的状态,并以相应的规定保障状态以一种可预测的形式发生变化。- 咱们期待以一种简略的“单向数据流”的形式治理利用,即 状态 -> 视图 -> 操作单向循环 的形式。但当咱们的利用遇到多个组件共享状态时,比方:多个视图依赖于同一状态或者来自不同视图的行为须要变更同一状态。此时单向数据流的简洁性很容易被毁坏。因而,咱们有必要把组件的共享状态抽取进去,以一个全局单例模式治理。通过定义和隔离状态治理中的各种概念并通过强制规定维持视图和状态间的独立性,咱们的代码将会变得更结构化且易保护。这是
vuex
存在的必要性,它和react
生态中的redux
之类是一个概念 Vuex
解决状态治理的同时引入了不少概念:例如state
、mutation
、action
等,是否须要引入还须要依据利用的理论状况掂量一下:如果不打算开发大型单页利用,应用Vuex
反而是繁琐冗余的,一个简略的store
模式就足够了。然而,如果要构建一个中大型单页利用,Vuex
根本是标配。- 我在应用
vuex
过程中感触到一些等
可能的诘问
vuex
有什么毛病吗?你在开发过程中有遇到什么问题吗?- 刷新浏览器,
vuex
中的state
会从新变为初始状态。解决方案 - 插件vuex-persistedstate
action
和mutation
的区别是什么?为什么要辨别它们?
action
中解决异步,mutation
不能够mutation
做原子操作action
能够整合多个mutation
的汇合mutation
是同步更新数据(外部会进行是否为异步形式更新数据的检测)$watch
严格模式下会报错action
异步操作,能够获取数据后调佣mutation
提交最终数据
- 流程程序:“相应视图—> 批改 State”拆分成两局部,视图触发
Action
,Action 再触发
Mutation`。 - 基于流程程序,二者表演不同的角色:
Mutation
:专一于批改State
,实践上是批改State
的惟一路径。Action
:业务代码、异步申请 - 角色不同,二者有不同的限度:
Mutation
:必须同步执行。Action
:能够异步,但不能间接操作State
Watch 中的 deep:true 是如何实现的
当用户指定了
watch
中的 deep 属性为true
时,如果以后监控的值是数组类型。会对对象中的每一项进行求值,此时会将以后watcher
存入到对应属性的依赖中,这样数组中对象发生变化时也会告诉数据更新
源码相干
get () {pushTarget(this) // 先将以后依赖放到 Dep.target 上
let value
const vm = this.vm
try {value = this.getter.call(vm, vm)
} catch (e) {if (this.user) {handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {throw e}
} finally {if (this.deep) { // 如果须要深度监控
traverse(value) // 会对对象中的每一项取值, 取值时会执行对应的 get 办法
}popTarget()}
Vue3 速度快的起因
Vue3.0 性能晋升体现在哪些方面
- 代码层面性能优化次要体现在全新响应式
API
,基于Proxy
实现,初始化工夫和内存占用均大幅改良; - 编译层面做了更多编译优化解决,比方
动态标记 pachFlag
(diff
算法减少了一个动态标记,只比照有标记的dom
元素)、事件减少缓存
、动态晋升
(对不参加更新的元素,会做动态晋升,只会被创立一次,之后会在每次渲染时候被不停的复用)等,能够无效跳过大量diff
过程; - 打包时更好的反对
tree-shaking
,因而整体体积更小,加载更快 ssr
渲染以字符串形式渲染
一、编译阶段
试想一下,一个组件构造如下图
<template>
<div id="content">
<p class="text"> 动态文本 </p>
<p class="text"> 动态文本 </p>
<p class="text">{message}</p>
<p class="text"> 动态文本 </p>
...
<p class="text"> 动态文本 </p>
</div>
</template>
能够看到,组件外部只有一个动静节点,残余一堆都是动态节点,所以这里很多 diff
和遍历其实都是不须要的,造成性能节约
因而,Vue3 在编译阶段,做了进一步优化。次要有如下:
diff
算法优化- 动态晋升
- 事件监听缓存
SSR
优化
1. diff 算法优化
Vue 2x
中的虚构dom
是进行全量的比照。Vue 3x
中新增了动态标记(PatchFlag
): 在与上次虚构结点进行比照的时候,值比照 带有patch flag
的节点,并且能够通过flag
的信息得悉以后节点要比照的具体内容化
Vue2.x 的 diff 算法
vue2.x
的 diff
算法叫做 全量比拟
,顾名思义,就是当数据扭转的时候,会从头到尾的进行vDom
比照,即便有些内容是永恒固定不变的
Vue3.0 的 diff 算法
vue3.0
的 diff
算法有个叫动态标记(PatchFlag
)的小玩意,啥是动态标记呢?简略点说,就是如果你的内容会变,我会给你一个flag
,下次数据更新的时候我间接来比照你,我就不比照那些没有标记的了
曾经标记动态节点的 p
标签在 diff
过程中则不会比拟,把性能进一步提高
export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock("div", null, [_createVNode("p", null, "'HelloWorld'"),
_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
// 下面这个 1 就是动态标记
]))
}
对于动态类型枚举如下
TEXT = 1 // 动静文本节点
CLASS=1<<1,1 // 2// 动静 class
STYLE=1<<2,// 4 // 动静 style
PROPS=1<<3,// 8 // 动静属性,但不蕴含类名和款式
FULLPR0PS=1<<4,// 16 // 具备动静 key 属性,当 key 扭转时,须要进行残缺的 diff 比拟。HYDRATE_ EVENTS = 1 << 5,// 32 // 带有监听事件的节点
STABLE FRAGMENT = 1 << 6, // 64 // 一个不会扭转子节点程序的 fragment
KEYED_ FRAGMENT = 1 << 7, // 128 // 带有 key 属性的 fragment 或局部子字节有 key
UNKEYED FRAGMENT = 1<< 8, // 256 // 子节点没有 key 的 fragment
NEED PATCH = 1 << 9, // 512 // 一个节点只会进行非 props 比拟
DYNAMIC_SLOTS = 1 << 10 // 1024 // 动静 slot
HOISTED = -1 // 动态节点
// 批示在 diff 算法中退出优化模式
BALL = -2
2. hoistStatic 动态晋升
Vue 2x
: 无论元素是否参加更新,每次都会从新创立。Vue 3x
: 对不参加更新的元素,会做动态晋升,只会被创立一次,之后会在每次渲染时候被不停的复用。这样就免去了反复的创立节点,大型利用会受害于这个改变,免去了反复的创立操作,优化了运行时候的内存占用
<p>HelloWorld</p>
<p>HelloWorld</p>
<p>{message}</p>
开启动态晋升前
export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock("div", null, [_createVNode("p", null, "'HelloWorld'"),
_createVNode("p", null, "'HelloWorld'"),
_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
开启动态晋升后编译后果
const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "'HelloWorld'", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "'HelloWorld'", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock("div", null, [
_hoisted_1,
_hoisted_2,
_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
能够看到开启了动态晋升后,间接将那两个内容为 helloworld
的p
标签申明在里面了,间接就拿来用了。同时 _hoisted_1
和_hoisted_2
被打上了 PatchFlag
,动态标记值为 -1
,非凡标记是负整数示意永远不会用于 Diff
3. cacheHandlers 事件监听缓存
- 默认状况下 绑定事件会被视为动静绑定,所以每次都会去追踪它的变动
- 然而因为是同一个函数,所以没有追踪变动,间接缓存起来复用即可
<div>
<button @click = 'onClick'> 点我 </button>
</div>
开启事件侦听器缓存之前:
export const render = /*#__PURE__*/_withId(function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock("div", null, [_createVNode("button", { onClick: _ctx.onClick}, "点我", 8 /* PROPS */, ["onClick"])
// PROPS=1<<3,// 8 // 动静属性,但不蕴含类名和款式
]))
})
这里有一个 8
,示意着这个节点有了动态标记,有动态标记就会进行diff
算法比照差别,所以会浪费时间
开启事件侦听器缓存之后:
export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createBlock("div", null, [
_createVNode("button", {onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))
}, "点我")
]))
}
上述发现开启了缓存后,没有了动态标记。也就是说下次 diff
算法的时候间接应用
4. SSR 优化
当动态内容大到一定量级时候,会用 createStaticVNode
办法在客户端去生成一个static node
,这些动态node
,会被间接innerHtml
,就不须要创建对象,而后依据对象渲染
<div>
<div>
<span> 你好 </span>
</div>
... // 很多个动态属性
<div>
<span>{{message}}</span>
</div>
</div>
编译后
import {mergeProps as _mergeProps} from "vue"
import {ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate} from "@vue/server-renderer"
export function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {const _cssVars = { style: { color: _ctx.color}}
_push(`<div${_ssrRenderAttrs(_mergeProps(_attrs, _cssVars))
}><div><span> 你好 </span>...<div><span> 你好 </span><div><span>${_ssrInterpolate(_ctx.message)
}</span></div></div>`)
}
二、源码体积
相比 Vue2
,Vue3
整体体积变小了,除了移出一些不罕用的API
,再重要的是Tree shanking
任何一个函数,如 ref
、reactive
、computed
等,仅仅在用到的时候才打包,没用到的模块都被摇掉,打包的整体体积变小
import {computed, defineComponent, ref} from 'vue';
export default defineComponent({setup(props, context) {const age = ref(18)
let state = reactive({name: 'test'})
const readOnlyAge = computed(() => age.value++) // 19
return {
age,
state,
readOnlyAge
}
}
});
三、响应式零碎
vue2
中采纳 defineProperty
来劫持整个对象,而后进行深度遍历所有属性,给每个属性增加 getter
和setter
,实现响应式
vue3
采纳 proxy
重写了响应式零碎,因为 proxy
能够对整个对象进行监听,所以不须要深度遍历
- 能够监听动静属性的增加
- 能够监听到数组的索引和数组
length
属性 - 能够监听删除属性
什么是递归组件?举个例子阐明下?
剖析
递归组件咱们用的比拟少,然而在 Tree
、Menu
这类组件中会被用到。
体验
组件通过组件名称援用它本人,这种状况就是递归组件
<template>
<li>
<div> {{model.name}}</div>
<ul v-show="isOpen" v-if="isFolder">
<!-- 留神这里:组件递归渲染了它本人 -->
<TreeItem
class="item"
v-for="model in model.children"
:model="model">
</TreeItem>
</ul>
</li>
<script>
export default {
name: 'TreeItem',
// ...
}
</script>
答复范例
- 如果某个组件通过组件名称援用它本人,这种状况就是递归组件。
- 理论开发中相似
Tree
、Menu
这类组件,它们的节点往往蕴含子节点,子节点构造和父节点往往是雷同的。这类组件的数据往往也是树形构造,这种都是应用递归组件的典型场景。 - 应用递归组件时,因为咱们并未也不能在组件外部导入它本人,所以设置组件
name
属性,用来查找组件定义,如果应用SFC
,则能够通过SFC
文件名推断。组件外部通常也要有递归完结条件,比方model.children
这样的判断。 - 查看生成渲染函数可知,递归组件查找时会传递一个布尔值给
resolveComponent
,这样理论获取的组件就是以后组件自身
原理
递归组件编译后果中,获取组件时会传递一个标识符 _resolveComponent("Comp", true)
const _component_Comp = _resolveComponent("Comp", true)
就是在传递maybeSelfReference
export function resolveComponent(
name: string,
maybeSelfReference?: boolean
): ConcreteComponent | string {return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name
}
resolveAsset
中最终返回的是组件本身:
if (!res && maybeSelfReference) {
// fallback to implicit self-reference
return Component
}
怎么了解 Vue 的单向数据流
数据总是从父组件传到子组件,子组件没有权力批改父组件传过来的数据,只能申请父组件对原始数据进行批改。这样会 避免从子组件意外扭转父级组件的状态,从而导致你的利用的数据流向难以了解
留神:在子组件间接用 v-model
绑定父组件传过来的 prop
这样是不标准的写法 开发环境会报正告
如果切实要扭转父组件的 prop
值,能够在 data
外面定义一个变量 并用 prop
的值初始化它 之后用$emit
告诉父组件去批改
有两种常见的试图扭转一个 prop 的情景 :
- 这个
prop
用来传递一个初始值;这个子组件接下来心愿将其作为一个本地的prop
数据来应用。在这种状况下,最好定义一个本地的data
属性并将这个prop
用作其初始值
props: ['initialCounter'],
data: function () {
return {counter: this.initialCounter}
}
- 这个
prop
以一种原始的值传入且须要进行转换。在这种状况下,最好应用这个prop
的值来定义一个计算属性
props: ['size'],
computed: {normalizedSize: function () {return this.size.trim().toLowerCase()}
}
为什么不倡议用 index 作为 key?
应用 index 作为 key 和没写基本上没区别,因为不论数组的程序怎么颠倒,index 都是 0, 1, 2… 这样排列,导致 Vue 会复用谬误的旧子节点,做很多额定的工作。
参考 前端进阶面试题具体解答
怎么缓存以后的组件?缓存后怎么更新
缓存组件应用 keep-alive
组件,这是一个十分常见且有用的优化伎俩,vue3
中 keep-alive
有比拟大的更新,能说的点比拟多
思路
- 缓存用
keep-alive
,它的作用与用法 - 应用细节,例如缓存指定 / 排除、联合
router
和transition
- 组件缓存后更新能够利用
activated
或者beforeRouteEnter
- 原理论述
答复范例
- 开发中缓存组件应用
keep-alive
组件,keep-alive
是vue
内置组件,keep-alive
包裹动静组件component
时,会缓存不流动的组件实例,而不是销毁它们,这样在组件切换过程中将状态保留在内存中,避免反复渲染DOM
<keep-alive>
<component :is="view"></component>
</keep-alive>
- 联合属性
include
和exclude
能够明确指定缓存哪些组件或排除缓存指定组件。vue3
中联合vue-router
时变动较大,之前是keep-alive
包裹router-view
,当初须要反过来用router-view
包裹keep-alive
<router-view v-slot="{Component}">
<keep-alive>
<component :is="Component"></component>
</keep-alive>
</router-view>
- 缓存后如果要获取数据,解决方案能够有以下两种
beforeRouteEnter
:在有vue-router 的
我的项目,每次进入路由的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){
next(vm=>{console.log(vm)
// 每次进入路由执行
vm.getData() // 获取数据})
},
actived
:在keep-alive
缓存的组件被激活的时候,都会执行actived
钩子
activated(){this.getData() // 获取数据
},
keep-alive
是一个通用组件,它外部定义了一个map
,缓存创立过的组件实例,它返回的渲染函数外部会查找内嵌的component
组件对应组件的vnode
,如果该组件在map
中存在就间接返回它。因为component
的is
属性是个响应式数据,因而只有它变动,keep-alive
的render
函数就会从新执行
Vue 中组件和插件有什么区别
1. 组件是什么
组件就是把图形、非图形的各种逻辑均形象为一个对立的概念(组件)来实现开发的模式,在 Vue 中每一个.vue 文件都能够视为一个组件
组件的劣势
- 升高整个零碎的耦合度,在放弃接口不变的状况下,咱们能够替换不同的组件疾速实现需要,例如输入框,能够替换为日历、工夫、范畴等组件作具体的实现
- 调试不便,因为整个零碎是通过组件组合起来的,在呈现问题的时候,能够用排除法间接移除组件,或者依据报错的组件疾速定位问题,之所以可能疾速定位,是因为每个组件之间低耦合,职责繁多,所以逻辑会比剖析整个零碎要简略
- 进步可维护性,因为每个组件的职责繁多,并且组件在零碎中是被复用的,所以对代码进行优化可取得零碎的整体降级
2. 插件是什么
插件通常用来为 Vue
增加全局性能。插件的性能范畴没有严格的限度——个别有上面几种:
- 增加全局办法或者属性。如:
vue-custom-element
- 增加全局资源:指令 / 过滤器 / 过渡等。如
vue-touch
- 通过全局混入来增加一些组件选项。如
vue-router
- 增加
Vue
实例办法,通过把它们增加到Vue.prototype
上实现。 - 一个库,提供本人的
API
,同时提供下面提到的一个或多个性能。如vue-router
3. 两者的区别
两者的区别次要体现在以下几个方面:
- 编写模式
- 注册模式
- 应用场景
3.1 编写模式
编写组件
编写一个组件,能够有很多形式,咱们最常见的就是 vue 单文件的这种格局,每一个 .vue
文件咱们都能够看成是一个组件
vue 文件规范格局
<template>
</template>
<script>
export default{...}
</script>
<style>
</style>
咱们还能够通过 template
属性来编写一个组件,如果组件内容多,咱们能够在内部定义 template
组件内容,如果组件内容并不多,咱们可间接写在 template
属性上
<template id="testComponent"> // 组件显示的内容
<div>component!</div>
</template>
Vue.component('componentA',{
template: '#testComponent'
template: `<div>component</div>` // 组件内容少能够通过这种模式
})
编写插件
vue
插件的实现应该裸露一个 install
办法。这个办法的第一个参数是 Vue
结构器,第二个参数是一个可选的选项对象
MyPlugin.install = function (Vue, options) {
// 1. 增加全局办法或 property
Vue.myGlobalMethod = function () {// 逻辑...}
// 2. 增加全局资源
Vue.directive('my-directive', {bind (el, binding, vnode, oldVnode) {// 逻辑...}
...
})
// 3. 注入组件选项
Vue.mixin({created: function () {// 逻辑...}
...
})
// 4. 增加实例办法
Vue.prototype.$myMethod = function (methodOptions) {// 逻辑...}
}
3.2 注册模式
组件注册
vue 组件注册次要分为 全局注册 与部分注册
全局注册通过 Vue.component
办法,第一个参数为组件的名称,第二个参数为传入的配置项
Vue.component('my-component-name', { /* ... */})
部分注册只需在用到的中央通过 components
属性注册一个组件
const component1 = {...} // 定义一个组件
export default {
components:{component1 // 部分注册}
}
插件注册
插件的注册通过 Vue.use()
的形式进行注册(装置),第一个参数为插件的名字,第二个参数是可抉择的配置项
Vue.use(插件名字,{ /* ... */} )
留神的是:
注册插件的时候,须要在调用 new Vue()
启动利用之前实现
Vue.use
会主动阻止屡次注册雷同插件,只会注册一次
4. 应用场景
- 组件 (Component) 是用来形成你的 App 的业务模块,它的指标是
App.vue
- 插件 (Plugin) 是用来加强你的技术栈的功能模块,它的指标是 Vue 自身
简略来说,插件就是指对 Vue
的性能的加强或补充
Composition API 与 Options API 有什么不同
剖析
Vue3
最重要更新之一就是 Composition API
,它具备一些列长处,其中不少是针对Options API
裸露的一些问题量身打造。是 Vue3
举荐的写法,因而把握好 Composition API
利用对把握好 Vue3
至关重要
What is Composition API?(opens new window)
Composition API
呈现就是为了解决 Options API 导致雷同性能代码扩散的景象
体验
Composition API
能更好的组织代码,上面用 composition api
能够提取为useCount()
,用于组合、复用
compositon api 提供了以下几个函数:
setup
ref
reactive
watchEffect
watch
computed
toRefs
- 生命周期的
hooks
答复范例
Composition API
是一组API
,包含:Reactivity API
、生命周期钩子
、依赖注入
,使用户能够通过导入函数形式编写vue
组件。而Options API
则通过申明组件选项的对象模式编写组件Composition API
最次要作用是可能简洁、高效复用逻辑。解决了过来Options API
中mixins
的各种毛病;另外Composition API
具备更加麻利的代码组织能力,很多用户喜爱Options API
,认为所有货色都有固定地位的选项搁置代码,然而单个组件增长过大之后这反而成为限度,一个逻辑关注点扩散在组件各处,造成代码碎片,保护时须要重复横跳,Composition API
则能够将它们无效组织在一起。最初Composition API
领有更好的类型推断,对 ts 反对更敌对,Options API
在设计之初并未思考类型推断因素,尽管官网为此做了很多简单的类型体操,确保用户能够在应用Options API
时取得类型推断,然而还是没方法用在mixins
和provide/inject
上Vue3
首推Composition API
,然而这会让咱们在代码组织上多花点心理,因而在抉择上,如果咱们我的项目属于中低复杂度的场景,Options API
仍是一个好抉择。对于那些大型,高扩大,强保护的我的项目上,Composition API
会取得更大收益
可能的诘问
Composition API
是否和Options API
一起应用?
能够在同一个组件中应用两个 script
标签,一个应用 vue3,一个应用 vue2 写法,一起应用没有问题
<!-- vue3 -->
<script setup>
// vue3 写法
</script>
<!-- 降级 vue2 -->
<script>
export default {data() {},
methods: {}}
</script>
为什么要应用异步组件
- 节俭打包出的后果,异步组件离开打包,采纳
jsonp
的形式进行加载,无效解决文件过大的问题。 - 外围就是包组件定义变成一个函数,依赖
import()
语法,能够实现文件的宰割加载。
components:{AddCustomerSchedule:(resolve)=>import("../components/AddCustomer") // require([])
}
原理
export function (Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string): VNode | Array<VNode> | void {
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor) // 默认调用此函数时返回 undefiend
// 第二次渲染时 Ctor 不为 undefined
if (Ctor === undefined) {
return createAsyncPlaceholder( // 渲染占位符 空虚构节点
asyncFactory,
data,
context,
children,
tag
)
}
}
}
function resolveAsyncComponent (factory: Function, baseCtor: Class<Component>): Class<Component> | void {if (isDef(factory.resolved)) {
// 3. 在次渲染时能够拿到获取的最新组件
return factory.resolved
}
const resolve = once((res: Object | Class<Component>) => {factory.resolved = ensureCtor(res, baseCtor)
if (!sync) {forceRender(true) //2. 强制更新视图从新渲染
} else {owners.length = 0}
})
const reject = once(reason => {if (isDef(factory.errorComp)) {factory.error = true forceRender(true)
}
})
const res = factory(resolve, reject)// 1. 将 resolve 办法和 reject 办法传入,用户调用 resolve 办法后
sync = false
return factory.resolved
}
子组件能够间接扭转父组件的数据么,阐明起因
这是一个实际知识点,组件化开发过程中有个 单项数据流准则
,不在子组件中批改父组件是个常识问题
思路
- 讲讲单项数据流准则,表明为何不能这么做
- 举几个常见场景的例子说说解决方案
- 联合实际讲讲如果须要批改父组件状态应该如何做
答复范例
- 所有的
prop
都使得其父子之间造成了一个单向上行绑定:父级prop
的更新会向下流动到子组件中,然而反过来则不行。这样会避免从子组件意外变更父级组件的状态,从而导致你的利用的数据流向难以了解。另外,每次父级组件产生变更时,子组件中所有的prop
都将会刷新为最新的值。这意味着你不应该在一个子组件外部扭转prop
。如果你这样做了,Vue
会在浏览器控制台中收回正告
const props = defineProps(['foo'])
// ❌ 上面行为会被正告, props 是只读的!
props.foo = 'bar'
- 理论开发过程中有两个场景会想要批改一个属性:
这个 prop 用来传递一个初始值;这个子组件接下来心愿将其作为一个本地的 prop 数据来应用。 在这种状况下,最好定义一个本地的 data
,并将这个 prop
用作其初始值:
const props = defineProps(['initialCounter'])
const counter = ref(props.initialCounter)
这个 prop 以一种原始的值传入且须要进行转换。 在这种状况下,最好应用这个 prop
的值来定义一个计算属性:
const props = defineProps(['size'])
// prop 变动,计算属性自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())
- 实际中如果的确想要扭转父组件属性应该
emit
一个事件让父组件去做这个变更。留神尽管咱们不能间接批改一个传入的对象或者数组类型的prop
,然而咱们还是可能间接改内嵌的对象或属性
Vue 组件之间通信形式有哪些
Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信 :
父子组件通信
、隔代组件通信
、兄弟组件通信
,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信
组件传参的各种形式
组件通信罕用形式有以下几种
-
props / $emit
实用 父子组件通信- 父组件向子组件传递数据是通过
prop
传递的,子组件传递数据给父组件是通过$emit
触发事件来做到的
- 父组件向子组件传递数据是通过
-
ref
与$parent / $children(vue3 废除)
实用 父子组件通信ref
:如果在一般的DOM
元素上应用,援用指向的就是DOM
元素;如果用在子组件上,援用就指向组件实例$parent / $children
:拜访拜访父组件的属性或办法 / 拜访子组件的属性或办法
-
EventBus($emit / $on)
实用于 父子、隔代、兄弟组件通信- 这种办法通过一个空的
Vue
实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件
- 这种办法通过一个空的
-
$attrs / $listeners(vue3 废除)
实用于 隔代组件通信$attrs
:蕴含了父作用域中不被prop
所辨认 (且获取) 的个性绑定 (class
和style
除外 )。当一个组件没有申明任何prop
时,这里会蕴含所有父作用域的绑定 (class
和style
除外 ),并且能够通过v-bind="$attrs"
传入外部组件。通常配合inheritAttrs
选项一起应用$listeners
:蕴含了父作用域中的 (不含.native
润饰器的)v-on
事件监听器。它能够通过v-on="$listeners"
传入外部组件
-
provide / inject
实用于 隔代组件通信- 先人组件中通过
provider
来提供变量,而后在子孙组件中通过inject
来注入变量。provide / inject
API 次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系
- 先人组件中通过
$root
实用于 隔代组件通信 拜访根组件中的属性或办法,是根组件,不是父组件。$root
只对根组件有用-
Vuex
实用于 父子、隔代、兄弟组件通信Vuex
是一个专为Vue.js
利用程序开发的状态管理模式。每一个Vuex
利用的外围就是store
(仓库)。“store”基本上就是一个容器,它蕴含着你的利用中大部分的状态 (state
)Vuex
的状态存储是响应式的。当Vue
组件从store
中读取状态的时候,若store
中的状态发生变化,那么相应的组件也会相应地失去高效更新。- 扭转
store
中的状态的惟一路径就是显式地提交 (commit
)mutation
。这样使得咱们能够不便地跟踪每一个状态的变动。
依据组件之间关系探讨组件通信最为清晰无效
- 父子组件:
props
/$emit
/$parent
/ref
- 兄弟组件:
$parent
/eventbus
/vuex
- 跨层级关系:
eventbus
/vuex
/provide+inject
/$attrs + $listeners
/$root
上面演示组件之间通信三种状况: 父传子、子传父、兄弟组件之间的通信
1. 父子组件通信
应用
props
,父组件能够应用props
向子组件传递数据。
父组件 vue
模板father.vue
:
<template>
<child :msg="message"></child>
</template>
<script>
import child from './child.vue';
export default {
components: {child},
data () {
return {message: 'father message';}
}
}
</script>
子组件 vue
模板child.vue
:
<template>
<div>{{msg}}</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
}
}
</script>
回调函数(callBack)
父传子:将父组件里定义的 method
作为 props
传入子组件
// 父组件 Parent.vue:<Child :changeMsgFn="changeMessage">
methods: {changeMessage(){this.message = 'test'}
}
// 子组件 Child.vue:<button @click="changeMsgFn">
props:['changeMsgFn']
子组件向父组件通信
父组件向子组件传递事件办法,子组件通过
$emit
触发事件,回调给父组件
父组件 vue
模板father.vue
:
<template>
<child @msgFunc="func"></child>
</template>
<script>
import child from './child.vue';
export default {
components: {child},
methods: {func (msg) {console.log(msg);
}
}
}
</script>
子组件 vue
模板child.vue
:
<template>
<button @click="handleClick"> 点我 </button>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
},
methods () {handleClick () {
//........
this.$emit('msgFunc');
}
}
}
</script>
2. provide / inject 跨级拜访先人组件的数据
父组件通过应用 provide(){return{}}
提供须要传递的数据
export default {data() {
return {
title: '我是父组件',
name: 'poetry'
}
},
methods: {say() {alert(1)
}
},
// provide 属性 可能为前面的后辈组件 / 嵌套的组件提供所须要的变量和办法
provide() {
return {
message: '我是先人组件提供的数据',
name: this.name, // 传递属性
say: this.say
}
}
}
子组件通过应用 inject:[“参数 1”,”参数 2”,…]
接管父组件传递的参数
<template>
<p> 曾孙组件 </p>
<p>{{message}}</p>
</template>
<script>
export default {
// inject 注入 / 接管先人组件传递的所须要的数据即可
// 接管到的数据 变量 跟 data 外面的变量一样 能够间接绑定到页面 {{}}
inject: ["message","say"],
mounted() {this.say();
},
};
</script>
3. $parent + $children 获取父组件实例和子组件实例的汇合
this.$parent
能够间接拜访该组件的父实例或组件- 父组件也能够通过
this.$children
拜访它所有的子组件;须要留神$children
并不保障程序,也不是响应式的
<!-- parent.vue -->
<template>
<div>
<child1></child1>
<child2></child2>
<button @click="clickChild">$children 形式获取子组件值 </button>
</div>
</template>
<script>
import child1 from './child1'
import child2 from './child2'
export default {data(){
return {total: 108}
},
components: {
child1,
child2
},
methods: {funa(e){console.log("index",e)
},
clickChild(){console.log(this.$children[0].msg);
console.log(this.$children[1].msg);
}
}
}
</script>
<!-- child1.vue -->
<template>
<div>
<button @click="parentClick"> 点击拜访父组件 </button>
</div>
</template>
<script>
export default {data(){
return {msg:"child1"}
},
methods: {
// 拜访父组件数据
parentClick(){this.$parent.funa("xx")
console.log(this.$parent.total);
}
}
}
</script>
<!-- child2.vue -->
<template>
<div>
child2
</div>
</template>
<script>
export default {data(){
return {msg: 'child2'}
}
}
</script>
4. $attrs + $listeners 多级组件通信
$attrs
蕴含了从父组件传过来的所有props
属性
// 父组件 Parent.vue:<Child :name="name" :age="age"/>
// 子组件 Child.vue:<GrandChild v-bind="$attrs" />
// 孙子组件 GrandChild
<p> 姓名:{{$attrs.name}}</p>
<p> 年龄:{{$attrs.age}}</p>
$listeners
蕴含了父组件监听的所有事件
// 父组件 Parent.vue:<Child :name="name" :age="age" @changeNameFn="changeName"/>
// 子组件 Child.vue:<button @click="$listeners.changeNameFn"></button>
5. ref 父子组件通信
// 父组件 Parent.vue:<Child ref="childComp"/>
<button @click="changeName"></button>
changeName(){console.log(this.$refs.childComp.age);
this.$refs.childComp.changeAge()}
// 子组件 Child.vue:data(){
return{age:20}
},
methods(){changeAge(){this.age=15}
}
6. 非父子, 兄弟组件之间通信
vue2
中废除了broadcast
播送和散发事件的办法。父子组件中能够用props
和$emit()
。如何实现非父子组件间的通信,能够通过实例一个vue
实例Bus
作为媒介,要互相通信的兄弟组件之中,都引入Bus
,而后通过别离调用 Bus 事件触发和监听来实现通信和参数传递。Bus.js
能够是这样:
// Bus.js
// 创立一个地方工夫总线类
class Bus {constructor() {this.callbacks = {}; // 寄存事件的名字
}
$on(name, fn) {this.callbacks[name] = this.callbacks[name] || [];
this.callbacks[name].push(fn);
}
$emit(name, args) {if (this.callbacks[name]) {this.callbacks[name].forEach((cb) => cb(args));
}
}
}
// main.js
Vue.prototype.$bus = new Bus() // 将 $bus 挂载到 vue 实例的原型上
// 另一种形式
Vue.prototype.$bus = new Vue() // Vue 曾经实现了 Bus 的性能
<template>
<button @click="toBus"> 子组件传给兄弟组件 </button>
</template>
<script>
export default{
methods: {toBus () {this.$bus.$emit('foo', '来自兄弟组件')
}
}
}
</script>
另一个组件也在钩子函数中监听 on
事件
export default {data() {
return {message: ''}
},
mounted() {this.$bus.$on('foo', (msg) => {this.message = msg})
}
}
7. $root 拜访根组件中的属性或办法
- 作用:拜访根组件中的属性或办法
- 留神:是根组件,不是父组件。
$root
只对根组件有用
var vm = new Vue({
el: "#app",
data() {
return {rootInfo:"我是根元素的属性"}
},
methods: {alerts() {alert(111)
}
},
components: {
com1: {data() {
return {info: "组件 1"}
},
template: "<p>{{info}} <com2></com2></p>",
components: {
com2: {
template: "<p> 我是组件 1 的子组件 </p>",
created() {this.$root.alerts()// 根组件办法
console.log(this.$root.rootInfo)// 我是根元素的属性
}
}
}
}
}
});
8. vuex
- 实用场景: 简单关系的组件数据传递
- Vuex 作用相当于一个用来存储共享变量的容器
state
用来寄存共享变量的中央getter
,能够减少一个getter
派生状态,(相当于store
中的计算属性),用来取得共享变量的值mutations
用来寄存批改state
的办法。actions
也是用来寄存批改 state 的办法,不过action
是在mutations
的根底上进行。罕用来做一些异步操作
小结
- 父子关系的组件数据传递抉择
props
与$emit
进行传递,也可抉择ref
- 兄弟关系的组件数据传递可抉择
$bus
,其次能够抉择$parent
进行传递 - 先人与后辈组件数据传递可抉择
attrs
与listeners
或者Provide
与Inject
- 简单关系的组件数据传递能够通过
vuex
寄存共享的变量
怎么监听 vuex 数据的变动
剖析
vuex
数据状态是响应式的,所以状态变视图跟着变,然而有时还是须要晓得数据状态变了从而做一些事件。- 既然状态都是响应式的,那天然能够
watch
,另外vuex
也提供了订阅的 API:store.subscribe()
答复范例
- 我晓得几种办法:
- 能够通过
watch
选项或者watch
办法监听状态 - 能够应用
vuex
提供的 API:store.subscribe()
watch
选项形式,能够以字符串模式监听$store.state.xx
;subscribe
形式,能够调用store.subscribe(cb)
, 回调函数接管mutation
对象和state
对象,这样能够进一步判断mutation.type
是否是期待的那个,从而进一步做后续解决。watch
形式简略好用,且能获取变动前后值,首选;subscribe
办法会被所有commit
行为触发,因而还须要判断mutation.type
,用起来略繁琐,个别用于vuex
插件中
实际
watch
形式
const app = createApp({
watch: {'$store.state.counter'() {console.log('counter change!');
}
}
})
subscribe
形式:
store.subscribe((mutation, state) => {if (mutation.type === 'add') {console.log('counter change in subscribe()!');
}
})
vue3.2 自定义全局指令、部分指令
// 在 src 目录下新建一个 directive 文件,在此文件夹下新建一个 index.js 文件夹,接着输出如下内容
const directives = (app) => {
// 这里是给元素获得名字,尽管是 focus,然而理论援用的时候必须以 v 结尾
app.directive('focus',{
// 这里的 el 就是获取的元素
mounted(el) {el.focus()
}
})
}
// 默认导出 directives
export default directives
// 在全局注册 directive
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import directives from './directives'
const app = createApp(App)
directives(app)
app.use(store).use(router).mount('#app')
<!-- 在你须要的页面进行自定义指令的应用 -->
<template>
<div class="container">
<div class="content">
<input type="text" v-focus>
内容
</div>
</div>
</template>
<script setup>
import {reactive, ref} from 'vue'
// const vMove:Directive = () =>{//}
</script>
在
vue3.2 setup
语法糖模式下,自定义指令变得及其简略
<input type="text" v-model="value" v-focus>
<script setup>
// 间接写,然而必须是 v 结尾
const vFocus = {mounted(el) {// 获取 input,并调用其 focus()办法
el.focus()}
}
</script>
<!-- demo 进去页面主动获取焦点,而后让盒子的色彩依据你 input 框输出的内容变色,并且作防抖解决 -->
<template>
<div class="container">
<div class="content" v-move="{background: value}">
内容
<input type="text" v-model="value" v-focus @keyup="see">
</div>
</div>
</template>
<script setup>
import {reactive, ref} from 'vue'
const value = ref('')
const vFocus = {mounted(el) {// 获取 input,并调用其 focus()办法
el.focus()}
}
let timer = null
const vMove = (el, binding) => {if (timer !== null) {clearTimeout(timer)
}
timer = setTimeout(() => {
el.style.background = binding.value.background
console.log(el);
}, 1000);
}
</script>
<style lang="scss" scoped>
.container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.content {
border-top: 5px solid black;
width: 200px;
height: 200px;
cursor: pointer;
border-left: 1px solid #ccc;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
}
</style>
Vue computed 实现
- 建设与其余属性(如:
data
、Store
)的分割; - 属性扭转后,告诉计算属性从新计算
实现时,次要如下
- 初始化
data
,应用Object.defineProperty
把这些属性全副转为getter/setter
。 - 初始化
computed
, 遍历computed
里的每个属性,每个computed
属性都是一个watch
实例。每个属性提供的函数作为属性的getter
,应用Object.defineProperty
转化。 Object.defineProperty getter
依赖收集。用于依赖发生变化时,触发属性从新计算。- 若呈现以后
computed
计算属性嵌套其余computed
计算属性时,先进行其余的依赖收集
Vue 中 diff 算法原理
DOM
操作是十分低廉的,因而咱们须要尽量地缩小 DOM
操作。这就须要找出本次 DOM
必须更新的节点来更新,其余的不更新,这个找出的过程,就须要利用 diff 算法
vue
的diff
算法是平级比拟,不思考跨级比拟的状况。外部采纳深度递归的形式 + 双指针 (头尾都加指针)
的形式进行比拟。
简略来说,Diff 算法有以下过程
- 同级比拟,再比拟子节点(依据
key
和tag
标签名判断) - 先判断一方有子节点和一方没有子节点的状况 (如果新的
children
没有子节点,将旧的子节点移除) - 比拟都有子节点的状况(外围
diff
) - 递归比拟子节点
- 失常
Diff
两个树的工夫复杂度是O(n^3)
,但理论状况下咱们很少会进行跨层级的挪动DOM
,所以Vue
将Diff
进行了优化,从O(n^3) -> O(n)
,只有当新旧children
都为多个子节点时才须要用外围的Diff
算法进行同层级比拟。 Vue2
的外围Diff
算法采纳了双端比拟
的算法,同时从新旧children
的两端开始进行比拟,借助key
值找到可复用的节点,再进行相干操作。相比React
的Diff
算法,同样状况下能够缩小挪动节点次数,缩小不必要的性能损耗,更加的优雅- 在创立
VNode
时就确定其类型,以及在mount/patch
的过程中采纳位运算来判断一个VNode
的类型,在这个根底之上再配合外围的Diff
算法,使得性能上较Vue2.x
有了晋升
vue3 中采纳最长递增子序列来实现
diff
优化
答复范例
思路
diff
算法是干什么的- 它的必要性
- 它何时执行
- 具体执行形式
- 拔高:说一下
vue3
中的优化
答复范例
Vue
中的diff
算法称为patching
算法,它由Snabbdo
m 批改而来,虚构DOM
要想转化为实在DOM
就须要通过patch
办法转换- 最后
Vue1.x
视图中每个依赖均有更新函数对应,能够做到精准更新,因而并不需要虚构DOM
和patching
算法反对,然而这样粒度过细导致Vue1.x
无奈承载较大利用;Vue 2.x
中为了升高Watcher
粒度,每个组件只有一个Watcher
与之对应,此时就须要引入patching
算法能力准确找到发生变化的中央并高效更新 vue
中diff
执行的时刻是组件内响应式数据变更触发实例执行其更新函数时,更新函数会再次执行render
函数取得最新的虚构DOM
,而后执行patc
h 函数,并传入新旧两次虚构 DOM,通过比对两者找到变动的中央,最初将其转化为对应的DOM
操作patch
过程是一个递归过程,遵循深度优先、同层比拟的策略;以vue3
的patch
为例- 首先判断两个节点是否为雷同同类节点,不同则删除从新创立
- 如果单方都是文本则更新文本内容
- 如果单方都是元素节点则递归更新子元素,同时更新元素属性
-
更新子节点时又分了几种状况
- 新的子节点是文本,老的子节点是数组则清空,并设置文本;
- 新的子节点是文本,老的子节点是文本则间接更新文本;
- 新的子节点是数组,老的子节点是文本则清空文本,并创立新子节点数组中的子元素;
- 新的子节点是数组,老的子节点也是数组,那么比拟两组子节点,更新细节 blabla
vue3
中引入的更新策略:动态节点标记等
vdom 中 diff 算法的繁难实现
以下代码只是帮忙大家了解 diff
算法的原理和流程
- 将
vdom
转化为实在dom
:
const createElement = (vnode) => {
let tag = vnode.tag;
let attrs = vnode.attrs || {};
let children = vnode.children || [];
if(!tag) {return null;}
// 创立元素
let elem = document.createElement(tag);
// 属性
let attrName;
for (attrName in attrs) {if(attrs.hasOwnProperty(attrName)) {elem.setAttribute(attrName, attrs[attrName]);
}
}
// 子元素
children.forEach(childVnode => {
// 给 elem 增加子元素
elem.appendChild(createElement(childVnode));
})
// 返回实在的 dom 元素
return elem;
}
- 用繁难
diff
算法做更新操作
function updateChildren(vnode, newVnode) {let children = vnode.children || [];
let newChildren = newVnode.children || [];
children.forEach((childVnode, index) => {let newChildVNode = newChildren[index];
if(childVnode.tag === newChildVNode.tag) {
// 深层次比照, 递归过程
updateChildren(childVnode, newChildVNode);
} else {
// 替换
replaceNode(childVnode, newChildVNode);
}
})
}
</details>
动静给 vue 的 data 增加一个新的属性时会产生什么?怎么解决?
Vue 不容许在曾经创立的实例上动静增加新的响应式属性
若想实现数据与视图同步更新,可采取上面三种解决方案:
Vue.set()
Object.assign()
$forcecUpdated()
Vue.set()
Vue.set(target, propertyName/index, value)
参数
{Object | Array} target
{string | number} propertyName/index
{any} value
返回值:设置的值
通过 Vue.set
向响应式对象中增加一个 property
,并确保这个新 property
同样是响应式的,且触发视图更新
对于 Vue.set
源码(省略了很多与本节不相干的代码)
源码地位:src\core\observer\index.js
function set (target: Array<any> | Object, key: any, val: any): any {
...
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
这里无非再次调用 defineReactive
办法,实现新增属性的响应式
对于 defineReactive
办法,外部还是通过 Object.defineProperty
实现属性拦挡
大抵代码如下:
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {get() {console.log(`get ${key}:${val}`);
return val
},
set(newVal) {if (newVal !== val) {console.log(`set ${key}:${newVal}`);
val = newVal
}
}
})
}
Object.assign()
间接应用 Object.assign()
增加到对象的新属性不会触发更新
应创立一个新的对象,合并原对象和混入对象的属性
this.someObject = Object.assign({},this.someObject,{newProperty1:1,newProperty2:2 ...})
$forceUpdate
如果你发现你本人须要在 Vue
中做一次强制更新,99.9% 的状况,是你在某个中央做错了事
$forceUpdate
迫使Vue
实例从新渲染
PS:仅仅影响实例自身和插入插槽内容的子组件,而不是所有子组件。
小结
- 如果为对象增加大量的新属性,能够间接采纳
Vue.set()
- 如果须要为新对象增加大量的新属性,则通过
Object.assign()
创立新对象 - 如果你切实不晓得怎么操作时,可采取
$forceUpdate()
进行强制刷新 (不倡议)
PS:vue3
是用过 proxy
实现数据响应式的,间接动静增加新属性仍能够实现数据响应式
v-if 和 v -show 区别
v-show
暗藏则是为该元素增加css--display:none
,dom
元素仍旧还在。v-if
显示暗藏是将dom
元素整个增加或删除- 编译过程:
v-if
切换有一个部分编译 / 卸载的过程,切换过程中适合地销毁和重建外部的事件监听和子组件;v-show
只是简略的基于css
切换 - 编译条件:
v-if
是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时,并不做操作,直到为真才渲染 v-show
由false
变为true
的时候不会触发组件的生命周期v-if
由false
变为true
的时候,触发组件的beforeCreate
、create
、beforeMount
、mounted
钩子,由true
变为false
的时候触发组件的beforeDestory
、destoryed
办法- 性能耗费:
v-if
有更高的切换耗费;v-show
有更高的初始渲染耗费
v-show 与 v -if 的应用场景
v-if
与v-show
都能管制dom
元素在页面的显示v-if
相比v-show
开销更大的(间接操作dom 节
点减少与删除)- 如果须要十分频繁地切换,则应用 v-show 较好
- 如果在运行时条件很少扭转,则应用
v-if
较好
v-show 与 v -if 原理剖析
v-show
原理
不论初始条件是什么,元素总是会被渲染
咱们看一下在 vue 中是如何实现的
代码很好了解,有 transition
就执行 transition
,没有就间接设置display
属性
// https://github.com/vuejs/vue-next/blob/3cd30c5245da0733f9eb6f29d220f39c46518162/packages/runtime-dom/src/directives/vShow.ts
export const vShow: ObjectDirective<VShowElement> = {beforeMount(el, { value}, {transition}) {
el._vod = el.style.display === 'none' ? '' : el.style.display
if (transition && value) {transition.beforeEnter(el)
} else {setDisplay(el, value)
}
},
mounted(el, { value}, {transition}) {if (transition && value) {transition.enter(el)
}
},
updated(el, { value, oldValue}, {transition}) {// ...},
beforeUnmount(el, { value}) {setDisplay(el, value)
}
}
v-if
原理
v-if
在实现上比 v-show
要简单的多,因为还有else
else-if
等条件须要解决,这里咱们也只摘抄源码中解决 v-if
的一小部分
返回一个 node
节点,render
函数通过表达式的值来决定是否生成DOM
// https://github.com/vuejs/vue-next/blob/cdc9f336fd/packages/compiler-core/src/transforms/vIf.ts
export const transformIf = createStructuralDirectiveTransform(/^(if|else|else-if)$/,
(node, dir, context) => {return processIf(node, dir, context, (ifNode, branch, isRoot) => {
// ...
return () => {if (isRoot) {
ifNode.codegenNode = createCodegenNodeForBranch(
branch,
key,
context
) as IfConditionalExpression
} else {
// attach this branch's codegen node to the v-if root.
const parentCondition = getParentCondition(ifNode.codegenNode!)
parentCondition.alternate = createCodegenNodeForBranch(
branch,
key + ifNode.branches.length - 1,
context
)
}
}
})
}
)
理解 history 有哪些办法吗?说下它们的区别
history
这个对象在html5
的时候新退出两个api
history.pushState()
和history.repalceState()
这两个API
能够在不进行刷新的状况下,操作浏览器的历史纪录。惟一不同的是,前者是新增一个历史记录,后者是间接替换以后的历史记录。
从参数上来说:
window.history.pushState(state,title,url)
//state:须要保留的数据,这个数据在触发 popstate 事件时,能够在 event.state 里获取
//title:题目,根本没用,个别传 null
//url:设定新的历史纪录的 url。新的 url 与以后 url 的 origin 必须是一样的,否则会抛出谬误。url 能够时绝对路径,也能够是相对路径。// 如 以后 url 是 https://www.baidu.com/a/, 执行 history.pushState(null, null, './qq/'),则变成 https://www.baidu.com/a/qq/,// 执行 history.pushState(null, null, '/qq/'),则变成 https://www.baidu.com/qq/
window.history.replaceState(state,title,url)
// 与 pushState 基本相同,但她是批改以后历史纪录,而 pushState 是创立新的历史纪录
另外还有:
window.history.back()
后退window.history.forward()
后退window.history.go(1)
后退或者后退几步
从触发事件的监听上来说:
pushState()
和replaceState()
不能被popstate
事件所监听- 而前面三者能够,且用户点击浏览器后退后退键时也能够
从 0 到 1 本人构架一个 vue 我的项目,说说有哪些步骤、哪些重要插件、目录构造你会怎么组织
综合实际类题目,考查实战能力。没有什么相对的正确答案,把平时工作的重点有条理的形容一下即可
思路
- 构建我的项目,创立我的项目根本构造
- 引入必要的插件:
- 代码标准:
prettier
,eslint
- 提交标准:
husky
,lint-staged` - 其余罕用:
svg-loader
,vueuse
,nprogress
- 常见目录构造
答复范例
- 从
0
创立一个我的项目我大抵会做以下事件:我的项目构建、引入必要插件、代码标准、提交标准、罕用库和组件 - 目前
vue3
我的项目我会用vite
或者create-vue
创立我的项目 - 接下来引入必要插件:路由插件
vue-router
、状态治理vuex/pinia
、ui
库我比拟喜爱element-plu
s 和antd-vue
、http
工具我会选axios
- 其余比拟罕用的库有
vueuse
,nprogress
,图标能够应用vite-svg-loader
- 上面是代码标准:联合
prettier
和eslint
即可 - 最初是提交标准,能够应用
husky
,lint-staged
,commitlint
- 目录构造我有如下习惯:
.vscode
:用来放我的项目中的vscode
配置 plugins
:用来放vite
插件的plugin
配置public
:用来放一些诸如 页头icon
之类的公共文件,会被打包到dist
根目录下src
:用来放我的项目代码文件api
:用来放http
的一些接口配置assets
:用来放一些CSS
之类的动态资源components
:用来放我的项目通用组件layout
:用来放我的项目的布局router
:用来放我的项目的路由配置store
:用来放状态治理Pinia
的配置utils
:用来放我的项目中的工具办法类views
:用来放我的项目的页面文件