目录
Vuex 如何知道 State 是通过 Mutation 修改还是外部修改
前言
本文汇总了vue常用知识点与常见面试题,附上本人对该vue知识介绍相关博客,适合收藏,经常回顾,也许每次阅读都会有进一步理解。
Vue 的优缺点
优点
1、创建单页面应用的轻量级Web应用框架
2、简单易用
3、双向数据绑定
4、组件化的思想
5、虚拟DOM
6、数据驱动视图
7、前后端分离
缺点
1、Vue在开发多页应时不够灵活,需要配置多入口
2、不支持IE8
SPA的理解
SPA是 Single-Page-Application
的缩写,翻译过来就是单页应用。在WEB页面初始化时一同加载Html、Javascript、Css。一旦页面加载完成,SPA不会因为用户操作而进行页面重新加载或跳转,取而代之的是利用路由机制实现Html内容的变换。
优点
1、良好的用户体验,内容更改无需重载页面
2、SPA相对服务端压力更小
3、前后端职责分离,架构清晰
缺点
1、由于前端渲染,搜索引擎不会解析JS,只能抓取首页未渲染的模板,不利于SEO
2、单页面应用,在加载页面的时候将JavaScript、CSS统一加载,所以首次加载耗时更多
3、单页面应用需在一个页面显示所有的内容,默认不支持浏览器的前进后退(前端路由机制解决了该窘境,Hash模式中Hash变化会被浏览器记录,History模式利用 H5 新增的pushState
和replaceState
方法可改变浏览器历史记录栈)
MVVM的理解
MVVM是Model-View-ViewModel
的缩写。Model 代表数据层,可定义修改数据、编写业务逻辑。View 代表视图层,负责将数据渲染成页面。ViewModel 负责监听数据层数据变化,控制视图层行为交互,简单讲,就是同步数据层和视图层的对象。ViewModel 通过双向绑定把 View 和 Model 层连接起来,且同步工作无需人为干涉,使开发人员只关注业务逻辑,无需频繁操作DOM,不需关注数据状态的同步问题。
具体细节参考文章
单向数据流的理解
我们经常说 Vue 的双向绑定,其实是在单向绑定的基础上给元素添加 input/change
事件,来动态修改视图。Vue 组件间传递数据仍然是单项的,即父组件传递到子组件。子组件内部可以定义依赖 props 中的值,但无权修改父组件传递的数据,这样做防止子组件意外变更父组件的状态,导致应用数据流向难以理解。
如果在子组件内部直接
更改prop,会遇到警告处理。
以下为2种定义依赖props中的值
1、通过 data 定义属性并将 prop 作为初始值
<script>
export default {
props: ['userName'],
data() {
return {
name: this.userName
}
}
}
</script>
2、 用 computed 计算属性去定义依赖 prop 的值
<script>
export default {
props: ['userName'],
computed: {
helloName() {
return "hello" + this.userName
}
}
}
</sciprt>
响应式原理
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data
选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。Object.defineProperty
是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
响应式原理3
个步骤:数据劫持、依赖收集、派发更新。
数据分为两类:对象、数组
对象
遍历对象,通过Object.defineProperty
为每个属性添加 getter 和 setter,进行数据劫持。getter 函数用于在数据读取时进行依赖收集,在对应的 dep 中存储所有的 watcher;setter 则是数据更新后通知所有的 watcher 进行更新。
核心源码
function defineReactive(obj, key, val, shallow) {
// 实例化一个 dep, 一个 key 对应一个 dep
const dep = new Dep()
// 获取属性描述符
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 通过递归的方式处理 val 为对象的情况,即处理嵌套对象
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// 拦截obj.key,进行依赖收集
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// Dep.target 是当前组件渲染的 watcher
if (Dep.target) {
// 将 dep 添加到 watcher 中
dep.depend()
if (childOb) {
// 嵌套对象依赖收集
childOb.dep.depend()
// 响应式处理 value 值为数组的情况
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
// 获取旧值
const value = getter ? getter.call(obj) : val
// 判断新旧值是否一致
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (getter && !setter) return
// 如果是新值,用新值替换旧值
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 新值做响应式处理
childOb = !shallow && observe(newVal)
// 当响应式数据更新,依赖通知更新
dep.notify()
}
})
}
数组
用数组增强的方式,覆盖原属性上默认的数组方法,保证在新增或删除数据时,通过 dep 通知所有的 watcher 进行更新。
核心源码
const arrayProto = Array.prototype
// 基于数组原型对象创建一个新的对象
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) {
const original = arrayProto[method]
// 分别在 arrayMethods 对象上定义7个方法
def(arrayMethods, method, function mutator (...args) {
// 先执行原生的方法
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 针对新增元素进行响应式处理
if (inserted) ob.observeArray(inserted)
// 数据无论是新增还是删除都进行派发更新
ob.dep.notify()
return result
})
})
手写观察者模式
let uid = 0
class Dep {
constructor() {
this.id = uid++
// 存储所有的 watcher
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
removeSub(sub) {
if(this.subs.length) {
const index = this.subs.indexOf(sub)
if(index > -1) return this.subs.splice(index, 1)
}
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
class Watcher {
constructor(name) {
this.name = name
}
update() {
console.log('更新')
}
}
手写发布订阅模式
与观察者模式相似,区别在于发布者和订阅者是解耦的,由中间的调度中心去与发布者和订阅者通信。Vue响应式原理个人更倾向于发布订阅模式。其中 Observer 是发布者,Watcher 是订阅者,Dep 是调度中心。
class EventEmitter {
constructor() {
this.events = {}
}
on(type, cb) {
if(!this.events[type]) this.events[type] = []
this.events[type].push(cb)
}
emit(type, ...args) {
if(this.events[type]) {
this.events[type].forEach(cb => {
cb(...args)
})
}
}
off(type, cb) {
if(this.events[type]) {
const index = this.events[type].indexOf(cb)
if(index > -1) this.events[type].splice(index, 1)
}
}
}
组件中的data为什么是一个函数
数据以函数返回值形式定义,当每复用一次组件,就会返回一份新的data。即给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。对象在栈中存储的都是地址,函数的作用就是属性私有化,保证组件修改自身属性时不会影响其他复用组件。
生命周期
生命周期 | 描述 |
---|---|
beforeCreate | vue实例初始化后,数据观测(data observer)和事件配置之前。data、computed、watch、methods都无法访问。 |
created | vue实例创建完成后立即调用 ,可访问 data、computed、watch、methods。未挂载 DOM,不能访问 、ref。 |
beforeMount | 在 DOM 挂载开始之前调用。 |
mounted | vue实例被挂载到 DOM。 |
beforeUpdate | 数据更新之前调用,发生在虚拟 DOM 打补丁之前。 |
updated | 数据更新之后调用。 |
beforeDestroy | 实例销毁前调用。 |
destroyed | 实例销毁后调用 。 |
调用异步请求可在created
、beforeMount
、mounted
生命周期中调用,因为相关数据都已创建。在不涉及到DOM操作时,最好的选择是在created
中调用。
具体细节参考文章
父组件与子组件生命周期钩子执行顺序
加载渲染过程
速记:父先创建,才能有子;子创建完成,父才完整。
顺序:父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
组件更新过程
顺序:父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
销毁过程
顺序:父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
父组件监听子组件生命周期钩子的方式
以mounted为例
1、$emit
// 父组件
<template>
<div class="parent">
<!-- 监听子组件mounted事件 -->
<Child @mounted="doSomething"/>
</div>
</template>
<script>
export default {
methods: {
doSomething() {
console.log('父组件监听到子组件 mounted 钩子函数')
}
}
}
</script>
//子组件
<template>
<div>
</div>
</template>
<script>
export default {
mounted() {
console.log('触发mounted事件...')
this.$emit("mounted")
}
}
</script>
2、@hook
// 父组件
<template>
<div class="parent">
<Child @hook:mounted="doSomething"/>
</div>
</template>
<script>
export default {
methods: {
doSomething() {
console.log('父组件监听到子组件 mounted 钩子函数')
}
}
}
</script>
//子组件
<template>
<div>
</div>
</template>
<script>
export default {
mounted() {
console.log('触发mounted事件...')
}
}
</script>
组件通信方式
父子组件
1、props、$emit
2、$parent、$children
跨级组件
1、$attrs、$listeners
2、provide、inject
父子、跨级、兄弟
1、EventBus(事件总线)
2、Vuex(状态管理)
扩展
1、缓存
2、路由
具体细节参考文章
v-on监听多个方法
<button v-on="{mouseenter: onEnter, mouseleave: onLeave}">鼠标进来1</button>
常用修饰符
表单修饰符
1、lazy::失去焦点后同步信息
2、trim:自动过滤首尾空格
3、number:输入值转为数值类型
事件修饰符
1、stop:阻止冒泡(js中stopPropagation)
2、prevent:阻止默认行为(js中preventDefault)
3、self:仅绑定元素自身触发
4、once:只触发一次
class、style的动态绑定方式
对象方式
<template>
<div :class="{ active: isActive }"></div>
<div :style="{ fontSize: fontSize }">
</template>
<script>
export default {
data() {
return {
isActive: true, //如果为false,则不绑定"active"类
fontSize: 30
}
}
}
</script>
数组方式
<template>
<div :class="[activeClass]"></div>
<div :style="[styleFontSize]">
</template>
<script>
export default {
data() {
return {
activeClass: 'active',
styleFontSize: {
fontSize: '30px'
}
}
}
}
</script>
v-show与v-if的区别
相同点
都是控制页面元素的显示与隐藏
不同点
1、v-show 控制的是元素的CSS(v-show="false"相当于display:none);v-if 是控制元素本身的添加或删除(即是否出现在DOM结构中)
2、v-show 由 false 变为 true 的时候不会触发组件的生命周期。v-if 由 false 变为 true 则会触发组件的beforeCreate
、create
、beforeMount
、mounted
钩子,由 true 变为 false 会触发组件的beforeDestory
、destoryed
方法
3、频繁切换时,使用v-show;不频繁切换、加快页面渲染、需要销毁元素使用v-if
为什么v-if不能和v-for一起使用
性能浪费,每次渲染都要先循环再进行条件判断,考虑用计算属性替代。
Vue2.x中v-for
比v-if
更高的优先级。
Vue3.x中v-if
比 v-for
更高的优先级。
computed与watch的区别与使用场景
computed
计算属性,依赖其他属性值,且值具备缓存的特性。只有它依赖的属性值发生改变,下一次获取的值才会重新计算。
适用于数值计算,并且依赖于其他属性时。因为可以利用缓存特性,避免每次获取值,都需要重新计算。
watch
观察属性,监听属性值变动。每当属性值发生变化,都会执行相应的回调。
适用于数据变化时执行异步或开销比较大的操作。
具体细节参考文章
slot插槽的理解与使用
slot 插槽,可以理解为slot
在组件模板中提前占据了位置。当复用组件时,使用相关的slot标签时,标签里的内容就会自动替换组件模板中对应slot标签的位置,作为承载分发内容的出口。
具体细节参考文章
Vue.$delete和delete的区别
Vue.$delete 是直接删除了元素,改变了数组的长度;delete 是将被删除的元素变成内 undefined
,其他元素键值不变。
Vue.$set如何解决对象新增属性不能响应的问题
Vue.$set的出现是由于Object.defineProperty
的局限性:无法检测对象属性的新增或删除。
源码位置:vue/src/core/observer/index.js
export function set(target, key, val) {
// 数组
if(Array.isArray(target) && isValidArrayIndex(key)) {
// 修改数组长度,避免索引大于数组长度导致splice错误
target.length = Math.max(target.length, key)
// 利用数组splice触发响应
target.splice(key, 1, val)
return val
}
// key 已经存在,直接修改属性值
if(key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = target.__ob__
// target 不是响应式数据,直接赋值
if(!ob) {
target[key] = val
return val
}
// 响应式处理属性
defineReactive(ob.value, key, val)
// 派发更新
ob.dep.notify()
return val
}
具体原理
1、若是数组,直接使用数组的 splice 方法触发响应式
2、若是对象,判断属性是否存在,对象是否是响应式
3、以上都不满足,最后通过 defineReactive 对属性进行响应式处理
具体细节参考文章
Vue.$nextTick的原理
nextTick:在下次 DOM 更新循环结束之后执行延迟回调。常用于修改数据后获取更新后的DOM。
源码位置:vue/src/core/util/next-tick.js
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
// 是否使用微任务标识
export let isUsingMicroTask = false
// 回调函数队列
const callbacks = []
// 异步锁
let pending = false
function flushCallbacks () {
// 表示下一个 flushCallbacks 可以进入浏览器的任务队列了
pending = false
// 防止 nextTick 中包含 nextTick时出现问题,在执行回调函数队列前,提前复制备份,清空回调函数队列
const copies = callbacks.slice(0)
// 清空 callbacks 数组
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let timerFunc
// 浏览器能力检测
// 使用宏任务或微任务的目的是宏任务和微任务必在同步代码结束之后执行,这时能保证是最终渲染好的DOM。
// 宏任务耗费时间是大于微任务,在浏览器支持的情况下,优先使用微任务。
// 宏任务中效率也有差距,最低的就是 setTimeout
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// 将 nextTick 的回调函数用 try catch 包裹一层,用于异常捕获
// 将包裹后的函数放到 callback 中
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
// pengding 为 false, 执行 timerFunc
if (!pending) {
// 关上锁
pending = true
timerFunc()
}
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
总结:
1、运用异步锁的概念,保证同一时刻任务队列中只有一个 flushCallbacks。当 pengding 为 false 的时候,表示浏览器任务队列中没有 flushCallbacks 函数;当 pengding 为 true 的时候,表示浏览器任务队列中已经放入 flushCallbacks;待执行 flushCallback 函数时,pengding 会被再次置为 false,表示下一个 flushCallbacks 可进入任务队列
2、环境能力检测,选择可选中效率最高的(宏任务/微任务)进行包装执行,保证是在同步代码都执行完成后再去执行修改 DOM 等操作
3、flushCallbacks 先拷贝再清空,为了防止nextTick嵌套nextTick导致循环不结束
具体细节参考如上文章
虚拟DOM的理解
虚拟 DOM 的出现解决了浏览器的性能问题。虚拟 DOM 是一个用 JS 模拟的 DOM 结构对象(Vnode),用于频繁更改 DOM 操作后不立即更新 DOM,而是对比新老 Vnode,更新获取最新的Vnode,最后再一次性映射成真实的 DOM。这样做的原因是操作内存中操作 JS 对象速度比操作 DOM 快很多。
举个例子
<div id="container">
<p>real dom </p>
<ul>
<li class="item">item 1</li>
<li class="item">item 2</li>
<li class="item">item 3</li>
</ul>
</div
用 JS 来模拟以上 DOM 节点实现虚拟 DOM
function Element(tagName, props, children) {
this.tageName = tagName
this.props = props || {}
this.children = children || []
this.key = props.key
let count = 0
this.children.forEach(child => {
if(child instanceof Element) count += child.count
count++
})
this.count = count
}
const tree = Element('div', { id: container }, [
Element('p', {}, ['real dom'])
Element('ul', {}, [
Element('li', { class: 'item' }, ['item1']),
Element('li', { class: 'item' }, ['item2']),
Element('li', { class: 'item' }, ['item3'])
])
])
虚拟 DOM 转为真实的节点
Element.prototype.render = function() {
let el = document.createElement(this.tagName)
let props = this.props
for(let key in props) {
el.setAttribute(key, props[key])
}
let children = this.children || []
children.forEach(child => {
let child = (child instanceof Element) ? child.render() : document.createTextNode(child)
el.appendChild(child)
})
return el
}
Diff算法原理
具体细节参考文章
key的作用
key 是 Vue 中 vnode 的唯一标记,我们的 diff 的算法中 sameVnode 和 updateChildren 中就使用到了 key。
sameVnode 用来判断是否为同一节点。常见的业务场景是一个列表,若 key 值是列表索引,在新增或删除的情况下会存在就地复用的问题。(简单说,复用了上一个在当前位置元素的状态)所以 key 值的唯一,确保 diff 更准确。
updateChildren 中当其中四种假设都未匹配,就需要依赖老节点的 key 和 索引创建关系映射表,再用新节点的 key 去关系映射表去寻找索引进行更新,这保证 diff 算法更加快速。
简单来说就是:根据key判断是否复用,实现高效渲染
动态组件 和 异步组件
动态组件通过is
特性实现。适用于根据数据、动态渲染的场景,即组件类型不确定。
具体细节参考官网
Vue.directive 有写过么,有哪些应用场景
Vue.directive 可以注册全局指令和局部指令。
指令定义函数提供如下钩子函数
1、bind:指令第一次绑定到元素时调用(只调用一次)
2、inserted: 被绑定元素插入父节点时使用(父节点存在即可调用)
3、update:被绑定元素所在模板更新时调用,不论绑定值是否变化。通过比较更新前后的绑定值
4、 componentUpdated: 被绑定元素所在模板完成一次更新周期时调用
5、unbind: 只调用一次,指令与元素解绑时调用
具体细节参考官网
Vue 过滤器
Vue 过滤器可用在两个地方:双花括号插值和 v-bind 表达式。
Vue3 中已经废弃这个特点。
过滤器分为 全局过滤器 和 局部过滤器。
局部过滤器
<template>
<div>{
{ name | formatName }}</div>
</template>
<script>
export default {
// 局部过滤器
filters: {
formatName: function(value) {
// 可基于源值做一些处理
return value
}
}
}
</script>
全局过滤器
Vue.filter('formatName', function(value) {
// 可基于源值做一些处理
return value
})
过滤器可串联,执行顺序从左到右,第二个过滤器输入值是第一个过滤器的输出值
<div>{
{ name | formatName1 | formatName2 }}</div>
关于mixin的理解,有什么应用场景
mixin 混入分全局混入和局部混入,本质是 JS 对象,如 data、components、computed、methods 等。
全局混入不推荐使用,会影响后续每个Vue实例的创建。局部混入可提取组件间相同的代码,进行逻辑复用。
适用场景:如多个页面具备相同
的悬浮定位浮窗,可尝试用 mixin 封装。
具体细节参考文章
介绍一下keep-alive
具体细节参考文章
Vue-Router 配置 404 页面
* 代表通配符,若放在任意路由前,会被先匹配,导致跳转到 404 页面,所以需将如下配置置于最后。
{
path: '*',
name: '404'
component: () => import('./404.vue')
}
Vue-Router 有哪几种导航守卫
全局路由守卫
在路由跳转前触发,可在执行 next 方法前做一些身份登录验证的逻辑
const router = new VueRouter({
// 相关配置
})
// 全局路由守卫(跳转每个路由页面前统一处理)
router.beforeEach((to, from, next) => {
...
// 必须执行 next 方法来触发路由跳转
next()
})
全局后置钩子
和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身
// 全局后置钩子(跳转每个路由页面后统一处理)
router.afterEach((to, from) => {
// ...
})
路由独享守卫
可在路由配置上直接定义 beforeEnter
const router = new VueRouter({
routes: [
{
path: '/home',
component: Home,
// 只有该路由独享
beforeEnter: (to, from, next) => {
}
},
{
path: '/about',
component: About
}
]
})
组件守卫
组件内可直接定义如下路由导航守卫
onst Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 不能获取组件实例 this
// 当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 当前路由改变,但是组件被复用时调用
// 可访问实例 this
},
beforeRouteLeave(to, from, next) {
// 导航离开组件时被调用
}
}
更多细节参考官网
API 参考 | Vue Routerhttps://router.vuejs.org/zh/api/#router-link
Vue-Router 完整的导航解析流程
1、导航被触发
2、在失活的组件里调用 beforeRouteLeave 守卫
3、调用全局 beforeEach 前置守卫
4、重用的组件调用 beforeRouteUpdate 守卫(2.2+)
5、路由配置调用 beforeEnter
6、解析异步路由组件
7、在被激活的组件里调用 beforeRouteEnter 守卫
8、调用全局的 beforeResolve 守卫(2.5+)
9、导航被确认
10、调用全局的 afterEach
11、触发 DOM 更新
12、调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入
推荐本人对Vue router的详细介绍博客
Vuex 的理解及使用
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,采用集中式存储管理应用的所有组件的状态。
主要解决如下 两个 问题
1、多个视图依赖同一状态。
2、来自不同视图的行为需要变更同一个状态。
其包含如下模块
State:定义并初始化全局状态。
Getter:依赖 State 中的状态,进行二次包装,不会影响 State 源数据。
Mutation:更改 State 状态的函数,必须是同步。
Action:用于提交 Mutation,可包含任意异步操作。
Module:若应用复杂,Store 会集中一个比较大的对象而显得臃肿,Module允许我们将 Store模块化管理。
当然,若应用比较简单,共享状态也比较少,可以用 Vue.observe 去替代 Vuex,省去安装一个库也挺好。
具体细节参考文章
Vuex 刷新后数据丢失怎么办
持久化缓存:localStorage、sessionStorage
Vuex 如何知道 State 是通过 Mutation 修改还是外部修改
Vuex 中修改 state 唯一渠道是执行 commit 方法,底层通过执行 this._withCommit(fn),且设置_committing标识符为 true,才能修改 state,修改完还需要将标识符置为 false。外部修改是无法设置标识位的,所以通过 watch 监听 state 变化,来判断修改的合法性。
Vue SSR 了解么
Vue SSR 项目中暂时还没有运用过,后续会写个 Demo 单独成文吧。这边搬运下其他答案。
SSR 服务端渲染,将 HTML 渲染工作放在服务端完成后,将 HTML 返回到浏览器端。
优点:SSR有更好的 SEO,首屏加载更快。
缺点:服务端负载大。
如果是内部系统,SSR其实没有太多必要。如果是对外的项目,维护高可用的node服务器是个难点。
Vue2 与 Vue3 的区别 ?Vue3有哪些优化点?
具体细节参考本人博客对Vue3的详细介绍
Vue 性能优化
1、非响应式数据通过 Object.freeze 冻结数据
2、嵌套层级不要过深
3、computed 和 watch 区别使用
4、v-if 和 v-show 区别使用
5、v-for 避免和 v-if 一起使用,且绑定 key 值要唯一
6、列表数据过多采用分页或者虚拟列表
7、组件销毁后清除定时器和事件
8、图片懒加载
9、路由懒加载
10、防抖、节流
11、按需引入外部库
12、keep-alive缓存使用
13、服务端渲染(SSR)和预渲染
文章评论