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状态管理图例

下面的图,意思是,只能顺时针管理对象,不建议破坏这个管理(即便可以破坏)

  1. <mark>一个原因是,破坏了这种管理模式,Devtools 就无法跟踪store的修改状态了</mark>(难调试)
  2. <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 核心概念

官网:https://vuex.vuejs.org/zh/guide/state.html#state

  • 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>:

  1. <mark>存数据方便</mark>
  2. <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 更新状态

Vuexstore 状态的更新唯一方式:提交 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)

Vuexstore 中的 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的基本使用

官网 - https://vuex.vuejs.org/zh/guide/actions.html

定义异步方法,第一个参数是 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中, 并且在成功或者失败后, 调用对应的 resolvereject .
OK, 我们来看下面的代码:

# Module:state分区

官方介绍:https://vuex.vuejs.org/zh/guide/modules.html (建议直接观看,因为下面描述大多来自这里)

Module是模块的意思, 为什么在 Vuex 中我们要使用模块呢?

<mark>当应用变得非常复杂时,store对象就有可能变得相当臃肿.</mark>

为了解决这个问题,

Vuex 允许我们将 store 分割成模块( Module ), 而每个模块拥有自己的 statemutationsactionsgetters

我们按照什么样的方式来组织模块呢?
我们来看下边的代码

## Module局部状态

上面的代码中, 我们已经有了整体的组织结构, 下面我们来看看具体的局部模块中的代码如何书写.
我们在 moduleA 中添加 statemutationsgetters
mutationgetters 接收的第一个参数是局部状态对象

## 命名空间:namespaced

默认情况下,模块内部的 actionmutationgetter 是注册在 <mark>全局命名空间</mark> 的——这样使得多个模块能够对同一 mutationaction 作出响应。

如果希望你的模块具有更高的封装度和复用性,你<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

如果你希望使用全局 stategetterrootStaterootGetters 会作为第三和第四参数传入 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) { ... }
    }
  }
}

# vuex 的项目结构

官方:https://vuex.vuejs.org/zh/guide/structure.html