状态管理
在以前,前端没有状态管理的概念。人们试图把MVC这个概念引入到前端中,把状态叫做模型。在很多场景下,这是一中很好的抽象。但是主要问题是我们缺乏一种以声明的方式将模型与渲染结合。
在Vue等前端框架中,优势是状态已经透明到链接的Vue中,当然这也有问题,因为它太容易将状态传播到任何地方,并在任何地方进行修改。随着项目规模的增大,跟踪储存状态的位置立即改变状态的位置会变得越来越困难。
总体而言,很多问题实际上是查明问题的来源并且没有适当的模式让你管理和状态有关的代码。这会导致你的应用变得越来越不可预测。因此,状态管理只是前端开发人员试图找出一种科学方法解决代码如何存放和状态如何管理的问题。从而当问题出现的时候可以更轻松地查明问题。
Flux就是第一个关于单向数据流的想法。
在Vue中一切都很直接,只需要将状态放入组件中即可。实际上,很多简单场景已经够用。状态管理库或状态管理模式不一定在每种情况下都需要。
VueX——Vue的状态管理模式
如果有多个组件需要访问同一个状态,问题会变得复杂。如果数据是应用中多个组件共享的,那么数据应该提取出来集中管理,而不是放到某个组件中。所以这就提出了使用库或者是一个模式的必要性。
VueX的本质就是Vue的生态系统,当然,历经多次迭代,处理基础概念,他还带来了其他工具,例如模块系统,插件系统。
Props传递
最基本的状态管理,将数据和视图分离,counter本身只负责接受一些属性和渲染它。这些实例都有共同的父组件。在很多情况下,最坏的情况下你可能需要管理所有状态。在根节点中,并通过Props传递下去。当组件树结构越来越大,这样的弊端会逐渐显现出来,就是需要手动地将props一层层传递下去。
<div id="app">
<counter :count="count"></counter>
<counter :count="count"></counter>
<counter :count="count"></counter>
<button @click="count++">increment</button>
</div>
<script>
// requirement: a counter component rendered 3 times
// the component takes the current count via props
// and a button that increments all 3 counters at once
new Vue({
el: '#app',
//状态
data: {
count: 0
},
components: {
Counter: {
props: {
count: Number
},
//视图
template: `<div>{{ count }}</div>`
}
}
})
</script>
对象共享
我们可以将一个对象独立出来作为共享对象作为我们的状态,这样就避免了参数在组件中的传递。
<div id="app">
<counter></counter>
<counter></counter>
<counter></counter>
<button @click="inc">increment</button>
</div>
<script>
// create a counter component (that doesn't take any props)
// all instances of it should share the same count state
// and a button that increments all counters at the same time
//定义一个独立状态
const state = {
count: 0,
}
const Counter = {
// Convert state into reactive object
//用data返回这个状态,这样Vue就可以给他加上响应式
data () {
return state
},
//渲染函数
render (h) {
// Proxy the object
return h('div', this.count)
}
}
//注册组件
new Vue({
el: '#app',
components: {
Counter
},
methods: {
inc () {
state.count++
}
}
})
</script>
共享实例
上一个思路是共享对象,那么我们能不能更近一步,使用实例进行状态共享。
<div id="app">
<counter></counter>
<counter></counter>
<counter></counter>
<button @click="inc">increment</button>
</div>
<script>
// copy and modify the first exercise to use a Vue instance as
// a shared store instead.
//把数据和方法直接在vue中定义,这样他们就会带上响应式
const state = new Vue({
data: {
count: 0
},
methods: {
inc () {
this.count++
}
}
})
//渲染组件
const Counter = {
render: h => h('div', state.count)
}
//注册组件和方法
new Vue({
el: '#app',
components: {
Counter
},
//这里有点绕,用这个函数包装state的函数,使得数据交换被隐藏了起来,会更为安全
methods: {
inc () {
state.inc()
}
},
})
</script>
Mutation
在上一个例子中我们成功将实例抽象出来进行共享。那么使用Vue实例去封装一些使用方法,最后导出不同的API又该怎么实现呢?
<div id="app">
<counter></counter>
<counter></counter>
<counter></counter>
<button @click="inc">increment</button>
</div>
<script>
//定义一个函数,它返回Vue实例
function createStore ({ state, mutations }) {
return new Vue({
data: {
//使其具有响应性
state
},
methods: {
commit (mutation) {
if (!mutations.hasOwnProperty(mutation)) {
throw new Error('Unknown mutation')
}
//解构mutations获取内部所有方法,给他们赋予状态
mutations[mutation](state)
}
}
})
}
const store = createStore({
state: { count: 0 },
mutations: {
inc (state) {
state.count++
}
}
})
const Counter = {
render (h) {
return h('div', store.state.count)
}
}
new Vue({
el: '#app',
components: { Counter },
methods: {
inc () {
store.commit('inc')
}
}
})
</script>
最后一步,模拟VueX
当完成了前面的练习时,模拟VueX已经呼之欲出了,通过封装一个app函数,在函数内实现挂载节点,共享状态,视图和数据。之后只需要调用这个函数,传入必要参数就可以模拟VueX的行为了。
<div id="app"></div>
<script>
//Vuex简单模拟
//el:Vue实例挂载的元素节点,model共享的数据,view视图,action动作函数
function app ({ el, model, view, actions }) {
const wrappedActions = {}
//给动作函数中的所有函数传入实例的响应式数据,直接传入函数的话,函数内部的值将不具有响应式
Object.keys(actions).forEach(key => {
const originalAction = actions[key]
wrappedActions[key] = () => {
vm.model = originalAction(vm.model)
}
})
//创建vue实例对象
const vm = new Vue({
//挂载节点
el,
//数据
data: {
//给它带上响应式
model
},
//渲染函数(视图)
render (h) {
return view(h, this.model, wrappedActions)
},
//动作函数
methods: actions
})
}
// voila
//这里就可以进行类似Vue创建实例的方法了
app({
el: '#app',
model: {
count: 0
},
actions: {
inc: ({ count }) => ({ count: count + 1 }),
dec: ({ count }) => ({ count: count - 1 })
},
view: (h, model, actions) => h('div', { attrs: { id: 'app' }}, [
model.count, ' ',
h('button', { on: { click: actions.inc }}, '+'),
h('button', { on: { click: actions.dec }}, '-')
])
})
</script>
上面的这些例子只是非常简单的vue实例,只是为了演示VueX的实现方式。事实上VueX具有更多的功能。例如VueX有action,getter,模块等概念。当然VueX的核心仍然是基于Vue实例实现响应性。