前端到底应该如何实现状态管理?

一些关于vuex和flux和状态管理的思考。

Flux 架构就像眼镜:您自会知道什么时候需要它。

依然是计数器作为案例。作为一个store首先需要保存状态和修改状态。

1
2
3
4
5
6
7
8
9
10
const store = {
state: {
count: 0
},
mutations: {
increase: () => {
store.state.count++
}
}
}

然后假设这个计数器需要和后端同步,所以需要记录网络请求状态,并且发起网络请求,根据请求结果更改状态。

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
const store = {
state: {
count: 0,
loading: false,
error: ''
},
mutations: {
increase: () => {
store.state.count++
},
setLoading: (loading) => {
store.state.loading = loading
},
setError: (error) => {
store.state.error = error
}
},
actions: {
remoteIncrease: async () => {
try {
store.mutations.setLoading(true)
store.mutations.setError('')
await fakeAPI.increase() // fakeAPI 见文末
store.mutations.increase()
} catch (err) {
store.mutations.setError(err)
} finally {
store.mutations.setLoading(false)
}
}
}
}

写到这里可以发现,mutations好像没有帮到什么忙。只是告诉了我们怎么可以格式化地更新store的内容,保证不会更新出一些奇奇怪怪的东西。但是至于是什么在更新store,为什么更新了,我们一无所知。所以我们mutations的内容不应该是state可以以怎样的方式更新,而是state为何更新。改写如下:

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
const store = {
state: {
count: 0,
loading: false,
error: ''
},
mutations: {
requestIncrease: () => {
store.state.loading = true
store.state.error = ''
},
successIncrease: () => {
store.state.count++
store.state.loading = false
},
failedIncrease: (error) => {
store.state.loading = false
store.state.error = error
}
},
actions: {
remoteIncrease: async () => {
try {
store.mutations.requestIncrease()
await fakeAPI.increase() // fakeAPI 见文末
store.mutations.successIncrease()
} catch (err) {
store.mutations.failedIncrease(err)
}
}
}
}

这就是vuex的工作逻辑。如果需要state更新的帮助函数,可以在mutations文件中定义一些,但是不要暴露为mutations。在vuex中,mutations和actions都应该是你更新state的原因,而不是对于如何更新state的描述。

为什么不自己写store而要用vuex?因为vuex有很多帮助函数很好用。比如mapState,vuex modules和各种logger。此外vuex还有更多高级功能。如果你不需要这些帮助函数和更高级功能,用自己的store完全ok。


顺便:不是很喜欢用计数器作为例子,我开始学的时候就分不清这个increase到底是想说“告诉state我要increase了,state需要自己改状态”还是说“state!你去把你的count给我increase一下”。前者是更改的原因,后者是具体操作。

参考

1
2
3
4
5
6
// 上文用到的 `fakeAPI`
const fakeAPI = {
increase: async () => {
if (Math.random() < 0.5) throw new Error('Network Error')
}
}