[TOC]
# 介绍 Vuex
[Vuex](vuex.vuejs.org/zh) 是一个 Vue 官方的状态管理库,主要是解决组件之间的数据共享问题。
Vue 中组件的数据交流可以通过2种方式:
- **props**:从父组件传递数据(状态)到子组件;
- **events**, 子组件更改父组件状态,或者使用 event bus。
有时状态会比这些更复杂,在这种情况下,一个好的选择是将状态集中到单个存储中。这就是Vuex 所做的。
# 为什么要使用 Vuex
在 Vue 中 Vuex 不是唯一选择(使用 Redux 也可),但是它是官方的,而且和 Vue 紧密集成。
使用React时,你不得不从众多可用的库中选择一个,因为生态系统非常庞大,而且没有实际的标准。最近 Redux 是最受欢迎的选择,MobX 紧随其后。对于Vue,我想说的是,除了 Vuex,你不需要寻找其他任何东西,尤其是在开始的时候。
Vuex 从 React 生态系统借鉴了很多想法,因为这是 Redux 流行的 Flux 模式。如果你知道 Flux 或 Redux已经,Vuex将非常熟悉。如果你没有,没问题——我将从头到尾解释每一个概念。
Vue 应用程序中的组件可以有自己的状态。例如,输入框将在本地存储输入的数据。
这是完美的,组件可以有本地状态,即使使用 Vuex。当你开始做很多工作来传递一个状态时,你知道你需要像 Vuex 这样的东西。
Vuex为状态提供了一个中央存储库存储,您可以通过请求存储库来更改状态。
依赖于特定状态块的每个组件都将使用存储上的 getter 来访问它,以确保一旦该状态块发生更改,它就会被更新。
使用 Vuex 会给应用程序带来一些复杂性,因为事情需要以某种方式正确地进行设置,但是如果这有助于解决无组织的混乱的 props 传递和事件系统,那么这是一个不错的选择。
# 开始
推荐在 [CodeSandbox](https://codesandbox.io/s/vue) 。它上面有一个 Vue CLI 的示例,可以直接把玩 vue,
![](https://img.kancloud.cn/31/7a/317a9376bd677d173f00ea436f46d07d_1280x800.png)
可以 **Add dependency** 按钮,然后输入 `vuex`,进行安装。
本地安装的话:
`npm install vuex` 或者 `yarn add vuex`
# 创建 Vuex store
推荐 新建 `src/store/store.js`文件,然后 初始化 Vuex ,然后编写以下代码:
```
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export const store = new Vuex.Store({})
```
![Vuex store](https://img.kancloud.cn/04/86/0486887cc00898fe6e221187732fe44f_1280x800.png)
通过 `Vuex.Store()` api 来创建一个 Vuex store 对象,并通过 `export` 导出;
# 一个store 示例
现在我们做个示例,有两个兄弟组件,一个有输入框,一个显示输入的内容。
当输入框的数据发生变化,另一个组件的显示内容也变化。
首先删除默认的 HelloWorld 组件,然后添加一个Form 组件,和一个 Display 组件。
```
<template>
<div>
<label for="flavor">Favorite ice cream flavor?</label>
<input name="flavor">
</div>
</template>
```
```
<template>
<div>
<p>You chose ???</p>
</div>
</template>
```
# 添加组件到 app
我们添加它们到 App.vue 中,替换原来的 HelloWorld 组件:
```
<template>
<div id="app">
<Form/>
<Display/>
</div>
</template>
<script>
import Form from './components/Form'
import Display from './components/Display'
export default {
name: 'App',
components: {
Form,
Display
}
}
</script>
```
# 添加状态到 store中
在 `store.js` 中 添加一个键名为 `state`的对象,该 `state` 对象 含一个值为空字符串的 `flavor` 属性。
```
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
flavor: ''
}
})
```
当用户一有输入,我们便更新该值!
# 添加一个 mutation
除了使用 **mutations**,其他操作无法更改状态。我们需要在 Form 组件中设置一个 mutation,用来通知 store 更改状态。
```
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
flavor: ''
},
mutations: {
change(state, flavor) {
state.flavor = flavor
}
}
})
```
# 添加一个 getter 来引用状态属性
以上操作之后,我们需要添加一种查看状态的方法。我们使用 **getter** 来实现。我们为 `flavor` 属性设置了一个 getter:
```
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
flavor: ''
},
mutations: {
change(state, flavor) {
state.flavor = flavor
}
},
getters: {
flavor: state => state.flavor
}
})
```
`getters` 是一个对象。这里的 `flavor` 是一个对象的属性,它接收了应用的状态作为参数,并且返回了状态的 `flavor`值。
# 添加 Vuex store 到 app
现在 整个 store 已经准备就绪,我们返回 `main.js` 文件中,我们需要导入该状态,并且使它在我们的app 中可用。
首先导入加:
```
import { store } from './store/store'
```
并且添加到我们 Vue 应用中:
```
new Vue({
el: '#app',
store,
components: { App },
template: '<App/>'
})
```
一旦我们添加这个,因为这是主要的Vue组件,每个Vue组件中的 `store` 变量将指向该 Vuex store。
# 通过 `commit` 在用户的操作中更新状态
当用户输入时,我们通过 `store.commit` api,来更新状态。
但是首先,我们需要一个方法,当用户输入时,用来被调用。我们使用 `@input` 而不是 `@change` 事件,因为后者只在焦点从输入框移开时触发,而 `@input` 则在每次按键时调用。
```
<template>
<div>
<label for="flavor">Favorite ice cream flavor?</label>
<input @input="changed" name="flavor">
</div>
</template>
<script>
export default {
methods: {
changed: function(event) {
alert(event.target.value)
}
}
}
</script>
```
现在我们有了 `flavor` 的值,然后我们使用 Vuex API:
```
<script>
export default {
methods: {
changed: function(event) {
this.$store.commit('change', event.target.value)
}
}
}
</script>
```
看到我们如何使用 `this.$store` 引用 store 了吗? 这是因为在主Vue组件初始化中包含了 store 对象。
`commit()` 方法接受一个突变(mutation)名(我们在Vuex store 中使用了 `change`)和一个有效负载(payload),它将作为回调函数的第二个参数传递给突变。
# 使用 getter 打印状态值
现在我们需要在显示模板中使用 `this.$store.getters.flavor` 引用这个值的getter。 `this` 可以被删除,因为我们在模板中,而 `this` 是隐式的。
```
<template>
<div>
<p>You chose {{ $store.getters.flavor }}</p>
</div>
</template>
```
# 页面刷新state数据初始化
解决思路:将state中的数据放在浏览器的 sessionStorage 和 localStorage 中
在实际的Vue项目中,当我们的应用遇到多个组件之间的共享问题时,通常会用到Vuex(状态管理工具,可以解决不同组件之间的数据共享和数据持久化)解决组件之间同一状态的共享问题
解决方法:
① 存储在 `sessionStorage `中
单页面应用,操作都是在一个页面跳转路由,因此`sessionStorage`较为合适,原因如下:
1. sessionStorage 可以保证打开页面时 sessionStorage 的数据为空;
2. 每次打开页面 localStorage 存储着上一次打开页面的数据,因此需要清空之前的数据。
vuex 中 state 数据的修改必须通过 mutation 方法进行修改,因此 mutation 修改 state的同时需要修改 sessionstorage,问题倒是可以解决但是感觉很麻烦,state中有很多数据,很多 mutation 修改state就要很多次 sessionstorage 进行修改,既然如此直接用sessionstorage解决不就行了,为何还要用vuex多此一举呢? vuex 的数据在每次页面刷新时丢失,是否可以在页面刷新前再将数据存储到sessionstorage中呢,是可以的,[beforeunload](https://www.w3cschool.cn/fetch_api/fetch_api-9vhu2oq0.html) 事件可以在页面刷新前触发,但是在每个页面中监听beforeunload事件感觉也不太合适,那么最好的监听该事件的地方就在 `app.vue` 中。
1. 在 `app.vue` 的 `created `方法中读取`sessionstorage`中的数据存储在store中,此时用`vuex.store`的[replaceState](https://vuex.vuejs.org/zh/api/#replacestate)方法,替换 `store `的根状态
2. 在`beforeunload`方法中将 `store.state` 存储到 `sessionstorage `中。
示例:
```
export default {
name: 'App',
created () {
// 在页面加载时读取sessionStorage里的状态信息
sessionStorage.getItem("store") && this.$store.replaceState(Object.assign({}, this.$store.state,JSON.parse(sessionStorage.getItem("store"))))
// 在页面刷新时将vuex里的信息保存到sessionStorage里
window.addEventListener("beforeunload",()=>{
sessionStorage.setItem("store",JSON.stringify(this.$store.state))
})
}
}
```
② 使用 [vuex-persistedstate](https://npm.io/package/vuex-persistedstate) 、[vuex-along](https://github.com/boenfu/vuex-along)、[vuex-persist](https://npm.io/package/vuex-persist) 插件
安装插件:
~~~
npm install vuex-persistedstate --save
~~~
配置:
在 store 的 `index.js` 中,手动插入并配置
```
import createPersistedState from "vuex-persistedstate"
const store =new Vuex.Store({
plugins: [createPersistedState()]
})
```
该插件默认持久化所有 state,当然也可以指定需要持久化的 state:
```
import createPersistedState from "vuex-persistedstate"
const store =new Vuex.store({
plugins: [createPersistedState({
storage:window.sessionStorage,
reducer(data) {
return {
// 设置只存储state中的myData
myData:data.myData
}
}
})]
})
```
# 总结
这是介绍 Vuex!
完整的工作源代码可以在 <https://codesandbox.io/s/zq7k7nkzkm> 找到。
在这个谜题中仍然有许多概念缺失:
- actions
- modules
- helpers
- plugins
但是你已经有了一些基础去阅读官方文档中的相关内容。
编码快乐!
- Introduction
- Introduction to Vue
- Vue First App
- DevTools
- Configuring VS Code for Vue Development
- Components
- Single File Components
- Templates
- Styling components using CSS
- Directives
- Events
- Methods vs Watchers vs Computed Properties
- Props
- Slots
- Vue CLI
- 兼容IE
- Vue Router
- Vuex
- 组件设计
- 组件之间的通信
- 预渲染技术
- Vue 中的动画
- FLIP
- lottie
- Unit test
- Vue3 新特性
- Composition API
- Reactivity
- 使用 typescript
- 知识点
- 附录
- 问题
- 源码解析
- 资源