嗯,有了Markdown之后我更喜欢记一些学习笔记了,当作是一个记录吧。今天来写写Vuex,当然我是边看边写的,应该是会有一些乱而且可能会有不正确的地方,见谅。(而且感觉我都是大量Copy官方文档的啊)

Vuex是干嘛的?

用过React的同学都知道Redux,这是React的状态管理库,那么Vuex就是Vue的状态管理库。

什么是状态管理?在React中我们每个组件的数据都需要我们用 this.state.xxx 来设置,但是在Vue中就没有this.state而是data,让我们没有一个很直观的认识。我们可以大概地理解,state其实就是data中储存的数据,绑定到View上进行渲染。

但是,每个组件的data只能在本组件内访问,那当我们需要一些某几个不同的组件都能访问的state时好像就显得比较麻烦了(感觉用props也是比较麻烦的),这时我们也许就需要Vuex来管理这种数据了。(用官方文档的话来讲,避免破坏单向数据流的简洁性

这里Copy一下官方的话,我们用Vuex把组件的共享状态抽取出来,以一个全局单例模式管理,这样就构成了一个state的tree

开始

这里有几个需要注意的地方:

  1. store在全局是唯一
  2. 不能直接改变store中的state,必须显式地commit mutation,这样也是为了方便我们跟踪每一个state的变化

这里copy一下文档的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})

//获取state对象
console.log(store.state.count);
//变更对象
store.commit('increment');

这样我们就有了一个简单的计数器啦~~

State

官方文档中推荐使用 computed 来获取store中的状态,当然这里有一些骚操作,我直接来贴一下最后推荐的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const app = new Vue({
el: '#app',
// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件(划重点)
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})


//现在子组件中可以通过 this.$store访问到数据了
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}

另外,在模块化项目中,可以用 mapState 辅助函数来生成计算属性,用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

export default {
// ...
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,

// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',

// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}

Getter

与Computed的初衷相同的是,有时候我们会想让某个经常被访问的state的值被缓存起来,Vuex中允许我们定义getter来获取它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
//Getter接受state作为第一个参数
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},

//也可以接受其他Getter作为第二个参数
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
},

//还可以返回一个函数,实现给getter传递参数,但是此时不会缓存结果
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
})

//访问
store.getters.doneTodos

另外,像上面的 mapState 一样,也提供了 mapGetter 方便映射

Mutation

在Vuex中,更改store中状态的唯一方法就是commit mutation(这里写在前面,mutation必须是同步事务,所以如果有两个回调函数调用它,是无法断言谁先调用的),写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})

//调用
store.commit('increment')

另外,我们可以通过payload来传入额外的参数。在大多数情况下,payload应该是一个对象,写法如下:

1
2
3
4
5
6
7
8
9
10
11
// ...
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}

//调用
store.commit('increment', {
amount: 10
})

同样,Vuex提供了 mapMutations 来映射mutation

Action

mutation不支持异步操作,但是Vuex提供了新的异步操作方式:Action。Action通过提交mutation来实现修改状态,写法好像和mutation并没有什么区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})

虽然写法没有什么区别,但是用法就有区别了。action通过store.dispatch方法来触发,并不能直接调用,这样做是为了能在Action中封装一些异步操作并且commit mutation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})

//更复杂的异步实例
actions: {
//同样接受commit,state和payload三个参数
checkout ({ commit, state }, products) {
// 把当前购物车的物品备份起来
const savedCartItems = [...state.cart.added]
// 发出结账请求,然后乐观地清空购物车
commit(types.CHECKOUT_REQUEST)
// 购物 API 接受一个成功回调和一个失败回调
shop.buyProducts(
products,
// 成功操作
() => commit(types.CHECKOUT_SUCCESS),
// 失败操作
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}

我们可以看到,上面的示例中使用了异步操作来改变状态,异步函数在Action中被调用。

在Vuex中,作者也提供了一些Action的嵌套写法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
actions: {
actionA ({ commit }) {
//返回一个Promise对象
return new Promise((resolve, reject) => {
setTimeout(() => {
//提交mutation
commit('someMutation')
resolve()
}, 1000)
})
}
}

//用法
store.dispatch('actionA').then(() => {
// ...
})

//在另一个Action中调用
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}

//利用async,await组合
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}

可见,Action的支持异步特性让我们能够异步修改store里的状态

目前来说这些就已经够用了,下面贴一个在表单中绑定store中状态的方法:

第一种是利用v-model来绑定本组件的state,再侦听input或者change事件来更新store中的状态:

1
<input :value="message" @input="updateMessage">
1
2
3
4
5
6
7
8
9
10
computed: {
...mapState({
message: state => state.obj.message
})
},
methods: {
updateMessage (e) {
this.$store.commit('updateMessage', e.target.value)
}
}

另外一个是利用带有setter的双向绑定计算属性:

1
<input v-model="message">
1
2
3
4
5
6
7
8
9
10
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}

嗯,Vuex初级学习大概就到这里了~~感觉还是不错的,这个全局状态管理还是挺有用的