-
最后一篇:网络模块封装
-
vuex 官网: https://vuex.vuejs.org/zh/
Vuex
# Vuex是做什么的?
官方解释: Vuex
是一个专为 Vue.js
应用程序开发的 <mark>状态管理模式</mark>。
- 它采用 集中式存储管理 <mark>应用的所有组件的状态</mark>,并以相应的规则 <mark>保证状态以一种可预测的方式发生变化</mark>。
Vuex
也集成到Vue
的官方调试工具devtools extension
,提供了诸如零配置的time-travel
调试、状态快照导入导出等高级调试功能。
## 状态管理到底是什么?
对<mark>多个组件的共享变量管理</mark>
共享
共享变量值
我们叫做 状态
<mark>这些状态 我们全部放在一个对象里面,作为属性</mark>。
可以理解成,<mark>把需要多个组件共享的变量全部存储在一个对象里面</mark>。
然后,<mark>将这个对象放在顶层的Vue
实例中,让其他组件可以使用</mark>。
.
那么,多个组件就可以共享这个对象中的所有变量属性了呢~
响应
如果只是共享,我们只需要在 Vue
原型上加一个属性就 ok了。
Vuex
还解决一个问题:<mark>响应式</mark>
响应式:
当<mark>共享变量</mark>在一个组件内被修改,其他组件内也会知道这个<mark>共享变量</mark>被修改,从而做出相应的响应。
## 管理什么状态呢?
什么状态需要被管理?
比如说:
- 登录状态:
token
- 用户信息:
姓名
/头像
… - 购物:商品的收藏、购物车信息等
即:<mark>需要在多个界面共享的信息!</mark>
OK
,从理论上理解了状态管理之后,让我们从实际的代码再来看看状态管理。
毕竟,Talk is cheap, Show me the code
.(来自Linus)
我们先来看看但界面的状态管理吧.
# 单界面的状态管理
我们知道,要在单个组件中进行状态管理是一件非常简单的事情
什么意思呢?我们来看下面的图片。
这图片中的三种东西,怎么理解呢?
State
:状态。View
:视图层,可以针对State
的变化,显示不同的信息。Actions
:这里的Actions
主要是用户的各种操作:点击、输入等等,会导致状态的改变。
比如下面这个简单的单页面案例
<template>
<div id="app">
<h2>{{message}}</h2>
<p>{{counter}}</p>
<button @click='counter--'>-</button>
<button @click='counter++'>+</button>
</div>
</template>
<script> export default { name: 'App', data () { return { message: '我是一个App组件', counter: 0 } } } </script>
<style> </style>
counter
需要某种方式被 <mark>记录</mark> 下来,也就是我们的State
。counter
目前的值 <mark>需要被显示</mark> 在界面中,也就是我们的View
部分。- 界面发生某些操作时(我们这里是用户的点击,也可以是用户的input),需要去更新状态,也就是我们的
Actions
# 多界面状态管理:使用Vuex
Vue
已经帮我们做好了单个界面的状态管理,但是如果是多个界面呢?
解决方案: <mark>使用 Vuex</mark> - 官方getting start
安装
npm install vuex --save
添加文件夹 @/store
存放 vuex
代码,
<mark>(官方说:因为Vuex的核心属性是Store,所以文件夹是Store,道理和router的文件夹是router一个道理。 官方原文)</mark>
另外,下面是官方推荐的 项目结构
文件 @/store/index.js
写 Vuex 的代码:
import Vue from 'vue'
import Vuex from 'vuex'
// 1. 安装插件
Vue.use(Vuex)
// 2. 创建对象
const store = new Vuex.Store({
state: {
counter: 0
},
mutations: {},
actions: {},
getters: {},
modules: {}
})
// 3. 导出store
export default store
@/main.js
中注册 store
import Vue from 'vue'
import App from './App'
import store from '@/store'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
store,
render: h => h(App)
})
就可以在我们的代码里面用了
@/App.vue
<template>
<div id="app">
<h2>{{message}}</h2>
<p>{{$store.state.counter}}</p>
<button @click='$store.state.counter--'>-</button>
<button @click='$store.state.counter++'>+</button>
<hello-vuex></hello-vuex>
</div>
</template>
<script> import HelloVuex from '@/components/HelloVuex' export default { name: 'App', data () { return { message: '我是一个App组件', counter: 0 } }, components: { HelloVuex } } </script>
<style> </style>
@/components/HelloVuex.vue
<template>
<div>
<div>{{$store.state.counter}}</div>
</div>
</template>
<script> export default { name: 'HelloVuex' } </script>
<style> </style>
实现一个点击计数的功能:
这样短短的代码,实现了需求。但是,<mark>官方不建议这样直接带store的内容</mark>,而是会用到Vuex给定的方法。
后面会对代码进行修改
.
官方的建议
# Vuex状态管理图例
下面的图,意思是,只能顺时针管理对象,不建议破坏这个管理(即便可以破坏)
- <mark>一个原因是,破坏了这种管理模式,
Devtools
就无法跟踪store的修改状态了</mark>(难调试) - <mark>第二个原因,
Mutations
是不能异步的,当出现异步时,必须通过Action
</mark> (如网络请求)
(图片的 Backend API 就是往后端请求API服务)
# vue-devtools下载与使用
https://blog.csdn.net/LawssssCat/article/details/104532995
如果是通过 mutation 修改的 state, 这里是会有记录的
# 多界面状态管理:优化 mutation
我们要优化前面代码,通过 mutation 来进行 store 的 state 的属性的修改!
(<mark>以后,修改state,至少都要通过mutation。异步情况,需要Actions调用mutation</mark>)
修改 @/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
// 1. 安装插件
Vue.use(Vuex)
// 2. 创建对象
const store = new Vuex.Store({
state: {
counter: 0
},
mutations: {
// 方法
increment (state) {
state.counter++
},
decrement(state) {
state.counter--
}
},
actions: {},
getters: {},
modules: {}
})
// 3. 导出store
export default store
修改 @/App.vue
<template>
<div id="app">
<h2>{{message}}</h2>
<p>{{$store.state.counter}}</p>
<button @click='subtraction'>-</button>
<button @click='addition'>+</button>
<hello-vuex></hello-vuex>
</div>
</template>
<script> import HelloVuex from '@/components/HelloVuex' export default { name: 'App', data () { return { message: '我是一个App组件', counter: 0 } }, components: { HelloVuex }, methods: { addition () { this.$store.commit('increment') }, subtraction () { this.$store.commit('decrement') } } } </script>
<style> </style>
# Vuex 核心概念
State
- 保存的状态(<mark>单一状态树概念</mark>)Getters
- 类似组件的计算属性Mutation
- 类似组件Methods
(但操作state
一定要经过mutation
)Action
- 处理异步操作Module
- 划分模块,对模块操作进行保存
下面 先讲 <mark>单一状态数</mark>
# State 单一状态树
英文:Single Source of Truth
也可以翻译成~单一数据源
即:用<mark>一个对象</mark>就包含了<mark>全部的应用层级状态</mark>。至此它便作为一个“唯一数据源 (SSOT)”而存在
好处:
- 单一状态树让我们能够 <mark>直接地定位任一特定的状态片段</mark>
- 在调试的过程中也能 <mark>轻易地取得整个当前应用状态</mark> 的快照。
白话:
<mark>把数据存到统一的地方好管理</mark>:
- <mark>存数据方便</mark>
- <mark>拿数据方便</mark>
所以,我们应该吧状态全部存到 store 的 state 里面
# Getters 计算状态 function(state){…}
类似计算属性,当数据不方便直接展示,需要使用 getters
例子:计算平方
@/store/index.js
添加 getters 方法
import Vue from 'vue'
import Vuex from 'vuex'
// 1. 安装插件
Vue.use(Vuex)
// 2. 创建对象
const store = new Vuex.Store({
state: {
counter: 0
},
mutations: {
// 方法
increment (state) {
state.counter++
},
decrement (state) {
state.counter--
}
},
getters: {
powerCounter (state) {
return state.counter * state.counter
}
},
actions: {},
modules: {}
})
// 3. 导出store
export default store
修改 @/App.vue
展示方法
<template>
<div id="app">
<h2>------------- App内容 -------------------------</h2>
<h2>{{message}}</h2>
<p>{{$store.state.counter}}</p>
<button @click='subtraction'>-</button>
<button @click='addition'>+</button>
<h2>------------- App内容:getters ------------------</h2>
<h2>{{$store.getters.powerCounter}}</h2>
<h2>------------- 另外一个组件内容 -------------------------</h2>
<hello-vuex></hello-vuex>
</div>
</template>
<script> import HelloVuex from '@/components/HelloVuex' export default { name: 'App', data () { return { message: '我是一个App组件', counter: 0 } }, components: { HelloVuex }, methods: { addition () { this.$store.commit('increment') }, subtraction () { this.$store.commit('decrement') } } } </script>
<style> </style>
结果
另外,getters 方法还能接收 getters , 调用同级的函数
## getters 接收参数:闭包妙用 return(arg)=>{…}
如果我要 <mark>动态的传参数给getters</mark> 怎么办?
没办法直接在getters属性方法上传入(因为参数已经定死了)
但<mark>我们可以用闭包特性,返回一个 function ,让外界调换function传值</mark>!
例子: 动态加法
# Multation 更新状态
Vuex
的 store
状态的更新唯一方式:提交 Mutation
Mutation
主要包括两部分:
- 字符串的<mark>事件类型</mark>(type)
- 一个<mark>回调函数</mark>(handler),该回调函数的第一个参数就是state。
## mutation 的定义方式:
## 通过mutation更新 - commit(type)
## Mutation传递参数 - commit(type,arg)
在通过 mutation
更新数据的时候, 有可能我们希望携带一些额外的参数
参数被称为是 mutation
的载荷( Payload
)
Mutation
中的代码: <mark>传入单个参数</mark>
如果,<mark>有很多参数需要传递.</mark>
这个时候, 我们<mark>通常会以对象的形式传递</mark>, 也就是 payload
是一个对象.
这个时候可以再从对象中取出相关的信息.
payload 载荷
## Mutation提交风格: payload - commit({type:…,arg:…})
上面的通过 commit
进行提交是一种普通的方式
Vue
还提供了另外一种风格, 它是一个包含 type
属性的对象
Mutation
中的处理方式是将整个 commit
的对象作为 payload
使用, 所以代码没有改变, 依然如下:
## Mutation响应规则 Vue.set(obj,newprop,value)
Vuex
的 store
中的 state
是响应式的, 当 state
中的数据发生改变时, Vue
组件会自动更新.
<mark>这就要求我们必须遵守一些Vuex对应的规则</mark>:
- 提前在
store
中初始化好所需的属性. - 当给
state
中的对象<mark>添加新属性</mark>时, 使用下面的方式:
方式一: 使用Vue.set(obj, ‘newProp’, 123)
方式二: 用心对象给旧对象重新赋值 - 当给
state
中的对象删除属性时
使用Vue.set(obj, ‘newProp’)
## Mutation常量类型 – [type常量](){...}
- 在
@/store
里面创建文件mutation-types.js
专门存放 mutation 的 type 常量 - 然后在 mutations 里面导入
mutation-types.js
文件, 用[type常量]() {...}
的方式 申明方法 - 最后,在使用 mutation 的地方,先导入
mutation-types.js
取常量,然后通过commit(xxx常量)
来调用 mutation
## Mutation 只能同步使用
通常情况下, Vuex
要求我们 Mutation
中的<mark>方法必须是同步方法</mark>.
- 主要的原因是当我们使用
devtools
时, 可以devtools
可以帮助我们捕捉mutation的快照
. - <mark>但是如果是异步操作, 那么
devtools
将不能很好的追踪这个操作什么时候会被完成.</mark>
比如我们之前的代码, 当执行更新时,
devtools
中会有如下信息:
如果有 <mark>异步方法</mark>(下图)
执行之后,devtool 的监控和实际结果对应不上了‘!’
# Actions 异步状态更新
我们强调, <mark>不要在Mutation中进行异步操作.</mark>
但是某些情况, 我们确实希望在 Vuex
中进行一些异步操作, 比如网络请求, 必然是异步的. 这个时候怎么处理呢?
Action
!!!
Action
类似于Mutation, 但是是用来代替 Mutation
进行异步操作的.
## Action的基本使用
定义异步方法,第一个参数是 context,上下文的意思(使用上和 state 差不多,但不是一个东西!!)
context — function(context, payload)
context
是和 store
对象具有相同方法和属性的对象.
也就是说, 我们可以通过 context
去进行 commit
相关的操作, 也可以获取 context.state
等.
<mark>但是注意, 这里它们并不是同一个对象, 为什么呢? 我们后面学习Modules的时候, 再具体说.</mark>
- <mark>这样的代码是否多此一举呢?</mark>
我们定义了actions, 然后又在actions中去进行commit, 这不是脱裤放屁吗?
事实上并不是这样, 如果在Vuex中有异步操作, 那么我们就可以在actions
中完成了.
## 颁发 - dispatcher(action, payload)
## Action返回的Promise
官方讲解 - https://vuex.vuejs.org/zh/guide/actions.html#%E7%BB%84%E5%90%88-action
前面我们学习 ES6
语法的时候说过, Promise
经常用于异步操作.
在Action中, 我们可以将异步操作放在一个Promise中, 并且在成功或者失败后, 调用对应的 resolve
或 reject
.
OK, 我们来看下面的代码:
# Module:state分区
官方介绍:https://vuex.vuejs.org/zh/guide/modules.html (建议直接观看,因为下面描述大多来自这里)
Module
是模块的意思, 为什么在 Vuex
中我们要使用模块呢?
<mark>当应用变得非常复杂时,store对象就有可能变得相当臃肿.</mark>
为了解决这个问题,
Vuex
允许我们将 store
分割成模块( Module
), 而每个模块拥有自己的 state
、 mutations
、 actions
、 getters
等
我们按照什么样的方式来组织模块呢?
我们来看下边的代码
## Module局部状态
上面的代码中, 我们已经有了整体的组织结构, 下面我们来看看具体的局部模块中的代码如何书写.
我们在 moduleA 中添加 state
、 mutations
、 getters
mutation
和 getters
接收的第一个参数是局部状态对象
## 命名空间:namespaced
默认情况下,模块内部的 action
、 mutation
和 getter
是注册在 <mark>全局命名空间</mark> 的——这样使得多个模块能够对同一 mutation
或 action
作出响应。
如果希望你的模块具有更高的封装度和复用性,你<mark>可以通过添加 namespaced: true
的方式使其成为带命名空间的模块</mark>。
<mark>当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名</mark>。
例如:
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// 模块内容(module assets)
state: { ... }, // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// 嵌套模块
modules: {
// 继承父模块的命名空间
myPage: {
state: { ... },
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// 进一步嵌套命名空间
posts: {
namespaced: true,
state: { ... },
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
## 访问全局:rootGetters
如果你希望使用全局 state
和 getter
, rootState
和 rootGetters
会作为第三和第四参数传入 getter
,也会通过 context 对象的属性传入 action。
<mark>若需要在全局命名空间内分发(dispatcher) action 或提交(commit) mutation,将 { root: true }
作为第三参数传给 dispatch 或 commit 即可</mark>。
modules: {
foo: {
namespaced: true,
getters: {
// 在这个模块的 getter 中,`getters` 被局部化了
// 你可以使用 getter 的第四个参数来调用 `rootGetters`
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter'
rootGetters.someOtherGetter // -> 'someOtherGetter'
},
someOtherGetter: state => { ... }
},
actions: {
// 在这个模块中, dispatch 和 commit 也被局部化了
// 他们可以接受 `root` 属性以访问根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
commit('someMutation') // -> 'foo/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
someOtherAction (ctx, payload) { ... }
}
}
}