Vuex 页面刷新数据丢失怎么解决
体验
可以从localStorage
中获取作为状态初始值:
const store = createStore({
state () {
return {
count: localStorage.getItem('count')
}
}
})
业务代码中,提交修改状态同时保存最新值:虽说实现了,但是每次还要手动刷新localStorage
不太优雅
store.commit('increment')
localStorage.setItem('count', store.state.count)
回答范例
vuex
只是在内存保存状态,刷新之后就会丢失,如果要持久化就要存起来localStorage
就很合适,提交mutation
的时候同时存入localStorage
,store
中把值取出作为state
的初始值即可。- 这里有两个问题,不是所有状态都需要持久化;如果需要保存的状态很多,编写的代码就不够优雅,每个提交的地方都要单独做保存处理。这里就可以利用
vuex
提供的subscribe
方法做一个统一的处理。甚至可以封装一个vuex
插件以便复用。 - 类似的插件有
vuex-persist
、vuex-persistedstate
,内部的实现就是通过订阅mutation
变化做统一处理,通过插件的选项控制哪些需要持久化
原理
可以看一下vuex-persist (opens new window)内部确实是利用subscribe
实现的
Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题 ?你能说说如下代码的实现原理么?
1)Vue为什么要用vm.$set() 解决对象新增属性不能响应的问题
- Vue使用了Object.defineProperty实现双向数据绑定
- 在初始化实例时对属性执行 getter/setter 转化
- 属性必须在data对象上存在才能让Vue将它转换为响应式的(这也就造成了Vue无法检测到对象属性的添加或删除)
所以Vue提供了Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)
2)接下来我们看看框架本身是如何实现的呢?
Vue 源码位置:vue/src/core/instance/index.js
export function set (target: Array<any> | Object, key: any, val: any): any {
// target 为数组
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 修改数组的长度, 避免索引>数组长度导致splcie()执行有误
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: any).__ob__
// target 本身就不是响应式数据, 直接赋值
if (!ob) {
target[key] = val
return val
}
// 对属性进行响应式处理
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
我们阅读以上源码可知,vm.$set 的实现原理是:
- 如果目标是数组,直接使用数组的 splice 方法触发相应式;
- 如果目标是对象,会先判读属性是否存在、对象是否是响应式,
- 最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理
defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法
Vue为什么没有类似于React中shouldComponentUpdate的生命周期?
考点: Vue的变化侦测原理
前置知识: 依赖收集、虚拟DOM、响应式系统
根本原因是Vue与React的变化侦测方式有所不同
React是pull的方式侦测变化,当React知道发生变化后,会使用Virtual Dom Diff进行差异检测,但是很多组件实际上是肯定不会发生变化的,这个时候需要用shouldComponentUpdate进行手动操作来减少diff,从而提高程序整体的性能.
Vue是pull+push的方式侦测变化的,在一开始就知道那个组件发生了变化,因此在push的阶段并不需要手动控制diff,而组件内部采用的diff方式实际上是可以引入类似于shouldComponentUpdate相关生命周期的,但是通常合理大小的组件不会有过量的diff,手动优化的价值有限,因此目前Vue并没有考虑引入shouldComponentUpdate这种手动优化的生命周期.
vue 中使用了哪些设计模式
1.工厂模式 - 传入参数即可创建实例
虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode
2.单例模式 - 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉
3.发布-订阅模式 (vue 事件机制)
4.观察者模式 (响应式数据原理)
5.装饰模式: (@装饰器的用法)
6.策略模式 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略
组件通信
组件通信的方式如下:
(1) props / $emit
父组件通过props
向子组件传递数据,子组件通过$emit
和父组件通信
1. 父组件向子组件传值
props
只能是父组件向子组件进行传值,props
使得父子组件之间形成了一个单向下行绑定。子组件的数据会随着父组件不断更新。props
可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。props
属性名规则:若在props
中使用驼峰形式,模板中需要使用短横线的形式
// 父组件
<template>
<div id="father">
<son :msg="msgData" :fn="myFunction"></son>
</div>
</template>
<script>
import son from "./son.vue";
export default {
name: father,
data() {
msgData: "父组件数据";
},
methods: {
myFunction() {
console.log("vue");
},
},
components: {
son },
};
</script>
// 子组件
<template>
<div id="son">
<p>{
{
msg }}</p>
<button @click="fn">按钮</button>
</div>
</template>
<script>
export default {
name: "son", props: ["msg", "fn"] };
</script>
2. 子组件向父组件传值
$emit
绑定一个自定义事件,当这个事件被执行的时就会将参数传递给父组件,而父组件通过v-on
监听并接收参数。
// 父组件
<template>
<div class="section">
<com-article
:articles="articleList"
@onEmitIndex="onEmitIndex"
></com-article>
<p>{
{
currentIndex }}</p>
</div>
</template>
<script>
import comArticle from "./test/article.vue";
export default {
name: "comArticle",
components: {
comArticle },
data() {
return {
currentIndex: -1, articleList: ["红楼梦", "西游记", "三国演义"] };
},
methods: {
onEmitIndex(idx) {
this.currentIndex = idx;
},
},
};
</script>
//子组件
<template>
<div>
<div
v-for="(item, index) in articles"
:key="index"
@click="emitIndex(index)"
>
{
{
item }}
</div>
</div>
</template>
<script>
export default {
props: ["articles"],
methods: {
emitIndex(index) {
this.$emit("onEmitIndex", index); // 触发父组件的方法,并传递参数index
},
},
};
</script>
(2)eventBus事件总线($emit / $on
)
eventBus
事件总线适用于父子组件、非父子组件等之间的通信,使用步骤如下: (1)创建事件中心管理组件之间的通信
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
(2)发送事件 假设有两个兄弟组件firstCom
和secondCom
:
<template>
<div>
<first-com></first-com>
<second-com></second-com>
</div>
</template>
<script>
import firstCom from "./firstCom.vue";
import secondCom from "./secondCom.vue";
export default {
components: {
firstCom, secondCom } };
</script>
在firstCom
组件中发送事件:
<template>
<div>
<button @click="add">加法</button>
</div>
</template>
<script>
import {
EventBus } from "./event-bus.js"; // 引入事件中心
export default {
data() {
return {
num: 0 };
},
methods: {
add() {
EventBus.$emit("addition", {
num: this.num++ });
},
},
};
</script>
(3)接收事件 在secondCom
组件中发送事件:
<template>
<div>求和: {
{
count }}</div>
</template>
<script>
import {
EventBus } from "./event-bus.js";
export default {
data() {
return {
count: 0 };
},
mounted() {
EventBus.$on("addition", (param) => {
this.count = this.count + param.num;
});
},
};
</script>
在上述代码中,这就相当于将num
值存贮在了事件总线中,在其他组件中可以直接访问。事件总线就相当于一个桥梁,不用组件通过它来通信。
虽然看起来比较简单,但是这种方法也有不变之处,如果项目过大,使用这种方式进行通信,后期维护起来会很困难。
(3)依赖注入(provide / inject)
这种方式就是Vue中的依赖注入,该方法用于父子组件之间的通信。当然这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方法来进行传值。就不用一层一层的传递了。
provide / inject
是Vue提供的两个钩子,和data
、methods
是同级的。并且provide
的书写形式和data
一样。
provide
钩子用来发送数据或方法inject
钩子用来接收数据或方法
在父组件中:
provide() {
return {
num: this.num
};
}
在子组件中:
inject: ['num']
还可以这样写,这样写就可以访问父组件中的所有属性:
provide() {
return {
app: this
};
}
data() {
return {
num: 1
};
}
inject: ['app']
console.log(this.app.num)
注意: 依赖注入所提供的属性是非响应式的。
(3)ref / $refs
这种方式也是实现父子组件之间的通信。
ref
: 这个属性用在子组件上,它的引用就指向了子组件的实例。可以通过实例来访问组件的数据和方法。
在子组件中:
export default {
data () {
return {
name: 'JavaScript'
}
},
methods: {
sayHello () {
console.log('hello')
}
}
}
在父组件中:
<template>
<child ref="child"></component-a>
</template>
<script>
import child from "./child.vue";
export default {
components: {
child },
mounted() {
console.log(this.$refs.child.name); // JavaScript
this.$refs.child.sayHello(); // hello
},
};
</script>
(4)$parent / $children
- 使用
$parent
可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法) - 使用
$children
可以让组件访问子组件的实例,但是,$children
并不能保证顺序,并且访问的数据也不是响应式的。
在子组件中:
<template>
<div>
<span>{
{
message }}</span>
<p>获取父组件的值为: {
{
parentVal }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: "Vue" };
},
computed: {
parentVal