事情发生在上周(2019-06-06)团队技术分享的时候。
起因在于一个问题:vue 中多个组件如何使用同一个变量 ,我们叫这个变量为 baseConfig
吧。
说实话我没想到那么多人不理解其中的知识。今天我整理一下发出来。
方案一:VUEX
什么是 Vuex
Vuex 是一个专为 Vue.js 应用程序开发的 状态管理 模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 可以帮助我们 管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
状态自管理应用包含以下几个部分:
- state,驱动应用的数据源;
- view,以声明方式将 state 映射到视图;
- actions,响应在 view 上的用户输入导致的状态变化。
以下是一个表示“单向数据流”理念的简单示意:
Vuex 的应用
其实看完了上面的介绍,我们就明白,这是一个非常符合我们需求的工具。那么我们就来看看怎么去应用。
-
mapState
辅助函数 当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性。 - 从下面的例子可以看到,我们子组件中的值是通过 计算属性 来从
store
中获取的。这样在通过mutations
等方式改变之后,我们的值也会动态更新。 - 当然,你的页面简单的时候,也不需要再去使用 Vuex,考虑一下后面的方案吧。
const store = new Vuex.Store({
state: {
baseConfig: {server_name: 'lilnong.top'}
}
})
// 创建一个 User 组件
const Serv = {template: `<div>{{ server_name}}</div>`,
computed: {server_name() {
// this.$store
return store.state.baseConfig.server_name
}
}
}
const app = new Vue({
el: '#app',
store, // 这样可以把 store 的实例注入所有的子组件
components: {Serv},
template: `
<div class="app">
<serv></serv>
</div>
`
})
方案二:父子组件传参
父传参的方式
baseConfig
需要定义在最外面,然后给所有的子组件都传递进去,当有改变的时候,子组件也会跟着改变。
{
data: {
baseConfig: {server_name: 'lilnong.top'}
}
}
<child :server_name="baseConfig.server_name" :baseConfig="baseConfig">
子组件接受参数的方式
每个子组件都需要接收。
{props:['server_name', 'baseConfig'],// 这种是无默认值,无类型检查的,正常使用不推荐这种写法
}
父子组件方案的优缺点
- 所有的父组件都需要传递参数,所有的子组件都要接收参数。
- 传入的问题可以通过,传入一个对象的所有参数来解决
方案二:路由组件传参
该方案也叫方案二,并不是我写错了 ,是因为他们的场景是一样。
在 Vue Router 的路由中,我们把组件配置在 routes
中,导致我们无法在模板之中传递参数。
这里我们需要使用他提供的 props
属性来传参,文档地址。jsRun 测试地址。lilnong.top 测试地址
const Foo = {template: '<div>foo</div>'}
const Bar = {template: '<div>bar</div>'}
const routes = [{ path: '/foo', component: Foo},
{path: '/bar', component: Bar}
]
const router = new VueRouter({routes // (缩写) 相当于 routes: routes
})
const app = new Vue({router}).$mount('#app')
<div id="app">
<h1>Hello App!</h1>
<p>
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<router-view></router-view>
</div>
方案三:全局对象(store 模式)
简单状态管理起步使用 – 官方文档
全局对象
就是如下使用,定义一个全局对象,然后修改这个全局对象就好了
const sourceOfTruth = {}
const vmA = new Vue({data: sourceOfTruth})
const vmB = new Vue({data: sourceOfTruth})
store 模式
原理上来讲,还是全局对象,但是 通过简单的规定,来明确数据流向
var store = {
debug: true,
state: {message: 'Hello!'},
setMessageAction (newValue) {if (this.debug) console.log('setMessageAction triggered with', newValue)
this.state.message = newValue
},
clearMessageAction () {if (this.debug) console.log('clearMessageAction triggered')
this.state.message = ''
}
}
var vmA = new Vue({
data: {privateState: {},
sharedState: store.state
}
})
var vmB = new Vue({
data: {privateState: {},
sharedState: store.state
}
})
争论核心:全局对象方案究竟行不行?原理?
好了,三种方案这里就已经介绍完了。那开始看看我们的争论:全局放个对象的方式不行(对方观点),数据更新时组件不会自动更新
先说原理,内存地址
vue 数据绑定的原理的原理大家都懂吧?通过 defineProperty
来劫持,Dep
收集依赖等等。
对于对象类型的数据,我们变量里面保存的其实是一个 指向堆的地址,我们来看下面的这个例子。
var obj = {};// 定义了一个对象,`obj` 存放的是一个地址
obj.a = 1;// 通过 `obj` 的地址,找到对象,然后给对象里面放了 `a=1` ;
var obj1 = obj;// 把 `obj` 的地址,给 `obj1` 复制了一下
obj1.a = 2;// 通过 `obj1` 的地址,找到对象,然后给对象里面放了 `a=2` ;
// 这个时候,对象里面存放的就是{a:2}//console.log(obj, obj1)
// 这里引出了另一个问题 深拷贝与浅拷贝
这里也就是我的核心原理。
- 定义一个对象(保存在 window 上)
- 通过对象变量保存的是地址的原理
- 我们在其他组件都用 window 上的对象以此来达到目的。
再说场景,细化问题
是不是看到上面的原理好简单?但是往往不是这么简单,下面咱们分析一下情况
- window 上没绑对象,而是其他类型这么办?比如说
null
、undefined
我也没辙 ,大哥,看好上面的原理,主要 原理就是地址引用。 -
对象覆盖,就是如下的这个赋值场景。
其实我理解你是想给obj
重新都赋值一下。obj={}; obj2 = obj; obj.a = 1; obj2 = {a:2,b:3};// 这里把 obj2 的地址换成了新的一个对象 //console.log(obj, obj2)
但是不能这样写,正确操作如下:
Object.assign(obj2, {a:2,b:3})
- 一个一个值的写
obj2.a=2;obj2.b=3;
-
后添加的属性,没有计入
vue
的数据观察队列(新手经常犯的错误)-
对象变更检测注意事项
于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除:
var vm = new Vue({ data: {a: 1} }) // `vm.a` 现在是响应式的 vm.b = 2 // `vm.b` 不是响应式的
对象解决方案
Vue.set(object, propertyName, value)
对象解决方案(实例内)this.$set(object, propertyName, value)
-
数组更新检测
将一些方法进行了封装push()、pop()、shift()、unshift()、splice()、sort()、reverse()
。
通过上面的方法来改变数组可以监听到改变。
由于 JavaScript 的限制,Vue 不能检测以下数组的变动:- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
- 当你利用索引直接设置一个数组项时,例如:
-
总结
共享数据三种方法
- vuex
大而全,使用复杂 - 组件值传递
原生自带,使用复杂,适合组件相关数据 - store
简单,不适合复杂项目。工程的话,还是推荐 vuex
对象引用需要注意的地方
- 不能给变量二次赋值
obj2={}
- 只有对象类型才是存地址,
Array
与Object
。String
与Null
等不包括在内 -
增加数据要注意 是否被观察到
- 对象:注意
Vue.set
- 数组:使用规定方法
- 对象:注意
测试地址,采用 setTimeout 来模拟异步操作。当时苦的一批,完了还没保存。性感码农,在线编程 。
成功的说服了在场的兄弟们,然后周四就拖堂了。
资料
- VUEX 中文站
- 状态管理 –vue 官网
微信公众号:前端 linong