Vue--Vuex

Vuex 就是前端为了方便数据的操作而建立的一个” 前端数据库“。模块间是不共享作用域的,那么B 模块想要拿到 A 模块的数据,我们会怎么做?我们会定义一个全局变量,叫aaa 吧,就是window.aaa。然后把A 模块要共享的数据作为属性挂到 B 模块上。这样我们在 B 模块中通过window.aaa 就可以拿到这个数据了。但是问题来了,B 模块拿到了共享的数据,就叫他xxx 吧?得了,名字太混乱了,咱先给它们都改下名字。那个全局变量既然是存东西的,就叫store 吧,共享的数据就叫state 吧,B 模块拿到了 A 模块的数据state,但是这个数据不是一成不变的呀,A 要操作这个数据的。那么我们是不是要在这个数据——state 改变的时候通知一下 B?那写个自定义事件吧。首先,你得能取吧?那么得有一套取数据的 API,我们给他们集中起个名字吧?既然是获取,那就叫getter 吧。我们还得存数据呀,是吧。存数据就是对数据库的修改,这些 API,我们也得给它起个名字,就叫mutation。vuex 生成的仓库也就这么出来了,所以我说 vuex 就是” 前端的数据库“。State 就是数据库。Mutations 就是我们把数据存入数据库的 API,用来修改state 的。getters 是我们从数据库里取数据的 API,既然是取,那么你肯定不能把数据库给改了吧?所以getters 得是一个”纯函数“,就是不会对原数据造成影响的函数。拿到了数据,总要做个处理吧,处理完了再存到数据库中。其实这就是action的过程。当然你也可以不做处理,直接丢到数据库。来看看 vuex 的数据流,通过action处理数据,然后通过mutation 把处理后的数据放入数据库(state)中,谁要用就通过getter从数据库(state)中取。

安装

npm install --save vuex

引入

import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)

实例化生成store的过程是

//store为实例化生成的
import store from './store'

new Vue({
  el: '#app',
  store,
  render: h => h(App)
})
const mutations = {...};
const actions = {...};
const state = {...};

Vuex.Store({
  state,
  actions,
  mutation
});

State

存储整个应用的状态数据.
想要获取对应的状态你就可以直接使用this.$store.state获取,也可以利用vuex提供的mapState辅助函数将state映射到计算属性中去

//我是组件
import {mapState} from 'vuex'

export default {
  computed: mapState({
    count: state => state.count
  })
}

Mutations

更改状态,本质就是用来处理数据的函数, 其接收唯一参数值state
store.commit(mutationName)是用来触发一个mutation的方法。需要记住的是,定义的mutation必须是同步函数

const mutations = {
  mutationName(state) {
    //在这里改变state中的数据
  }
}

在组件中触发:

//我是一个组件
export default {
  methods: {
    handleClick() {
      this.$store.commit('mutationName')
    }
  }
}

或者使用辅助函数mapMutations直接将触发函数映射到methods上,这样就能在元素事件绑定上直接使用了

import {mapMutations} from 'vuex'

//我是一个组件
export default {
  methods: mapMutations([
    'mutationName'
  ])
}

Actions

Actions也可以用于改变状态,不过是通过触发mutation实现的,重要的是可以包含异步操作。其辅助函数是mapActionsmapMutations类似,也是绑定在组件的methods上的。如果选择直接触发的话,使用this.$store.dispatch(actionName)方法。

//定义Actions
const actions = {
  actionName({ commit }) {
    //dosomething
    commit('mutationName')
  }
}

在组件中使用

import {mapActions} from 'vuex'

//我是一个组件
export default {
  methods: mapActions([
    'actionName',
  ])
}

Getters

有些状态需要做二次处理,就可以使用getters。通过this.$store.getters.valueName对派生出来的状态进行访问。或者直接使用辅助函数mapGetters将其映射到本地计算属性中去。

const getters = {
  strLength: state => state.aString.length
}
//上面的代码根据aString状态派生出了一个strLength状态

在组件中使用

import {mapGetters} from 'vuex'

//我是一个组件
export default {
  computed: mapGetters([
    'strLength'
  ])
}

Plugins

插件就是一个钩子函数,在初始化store的时候引入即可。比较常用的是内置的logger插件,用于作为调试使用。

import createLogger from 'vuex/dist/logger'
const store = Vuex.Store({
  ...
  plugins: [createLogger()]
})

Example

npm install --save vuex

该步骤完成之后,我们需要在 src 目录下创建名为 store 的目录来存放状态管理相关代码,首先创建 index.js:

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {

  },
  actions: {

  },
  mutations: {

  },
  getters: {

  }
})
export default store

然后在 main.js 文件中我们需要将该 Store 实例添加到构造的 Vue 实例中:

import store from './store'
/* eslint-disable no-new */
new Vue({
  template: `
  <div>
    <navbar />
    <section class="section">
      <div class="container is-fluid">
        <router-view></router-view>
      </div>
    </section>
  </div>
  `,
  router,
  store,
  components: {
    navbar
  }
}).$mount('#app')

然后,我们需要去完善 Store 定义:

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    projects: []
  },
  actions: {
    LOAD_PROJECT_LIST: function ({ commit }) {
      axios.get('/secured/projects').then((response) => {
        commit('SET_PROJECT_LIST', { list: response.data })
      }, (err) => {
        console.log(err)
      })
    }
  },
  mutations: {
    SET_PROJECT_LIST: (state, { list }) => {
      state.projects = list
    }
  },
  getters: {
    openProjects: state => {
      return state.projects.filter(project => !project.completed)
    }
  }
})
export default store

在本项目中,我们将原本存放在组件内的项目数组移动到 Store 中,并且将所有关于状态的改变都通过 Action 进行而不是直接修改:

// /src/components/projectList.vue
<template lang="html">
  <div class="">
    <table class="table">
      <thead>
        <tr>
          <th>Project Name</th>
          <th>Assigned To</th>
          <th>Priority</th>
          <th>Completed</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="item in projects">
          <td>{{item.name}}</td>
          <td>{{item.assignedTo}}</td>
          <td>{{item.priority}}</td>
          <td><i v-if="item.completed" class="fa fa-check"></i></td>
        </tr>
      </tbody>
    </table>
  </div>
</template>
<script>
import { mapState } from 'vuex'
export default {
  name: 'projectList',
  computed: mapState([
    'projects'
  ])
}
</script>
<style lang="css">
</style>

这个模板还是十分直观,我们通过computed对象来访问 Store 中的状态信息。值得一提的是这里的mapState函数,这里用的是简写,完整的话可以直接访问 Store 对象:

computed: {
  projects () {
    return this.$store.state.projects
  }
}

mapState 是 Vuex 提供的简化数据访问的辅助函数。我们视线回到 project.vue 容器组件,在该组件中调用this.$store.dispatch('LOAD_PROJECT_LIST)来触发从服务端中加载项目列表:

<template lang="html">
  <div id="projects">
    <div class="columns">
      <div class="column is-half">
        <div class="notification">
          Project List
        </div>
        <project-list />
      </div>
    </div>
  </div>
</template>
<script>
import projectList from '../components/projectList'
export default {
  name: 'projects',
  components: {
    projectList
  },
  mounted: function () {
    this.$store.dispatch('LOAD_PROJECT_LIST')
  }
}
</script>

当我们启动应用时,Vuex 状态管理容器会自动在数据获取之后渲染整个项目列表。现在我们需要添加新的 Action 与 Mutation 来创建新的项目:

// under actions:
ADD_NEW_PROJECT: function ({ commit }) {
  axios.post('/secured/projects').then((response) => {
    commit('ADD_PROJECT', { project: response.data })
  }, (err) => {
    console.log(err)
  })
}
// under mutations:
ADD_PROJECT: (state, { project }) => {
  state.projects.push(project)
}

然后我们创建一个简单的用于添加新的项目的组件 addProject.vue:

<template lang="html">
  <button type="button" class="button" @click="addProject()">Add New Project</button>
</template>
<script>
export default {
  name: 'addProject',
  methods: {
    addProject () {
      this.$store.dispatch('ADD_NEW_PROJECT')
    }
  }
}
</script>

该组件会派发某个 Action 来添加组件,我们需要将该组件引入到 projects.vue 中:

<template lang="html">
  <div id="projects">
    <div class="columns">
      <div class="column is-half">
        <div class="notification">
          Project List
        </div>
        <project-list />
        <add-project />
      </div>
    </div>
  </div>
</template>
<script>
import projectList from '../components/projectList'
import addProject from '../components/addProject'
export default {
  name: 'projects',
  components: {
    projectList,
    addProject
  },
  mounted: function () {
    this.$store.dispatch('LOAD_PROJECT_LIST')
  }
}
</script>

重新运行下该应用会看到服务端返回的创建成功的提示,现在我们添加另一个功能,就是允许用户将某个项目设置为已完成。我们现在添加新的组件 completeToggle.vue:

<template lang="html">
  <button type="button" class="button"  @click="toggle(item)">
    <i class="fa fa-undo" v-if="item.completed"></i>
    <i class="fa fa-check-circle" v-else></i>
  </button>
</template>
<script>
export default {
  name: 'completeToggle',
  props: ['item'],
  methods: {
    toggle (item) {
      this.$store.dispatch('TOGGLE_COMPLETED', { item: item })
    }
  }
}
</script>

该组件会展示一个用于切换项目是否完成的按钮,我们通过 Props 传入具体的项目信息然后通过触发 TOGGLE_COMPLETED Action 来使服务端进行相对应的更新与相应:

// actions
TOGGLE_COMPLETED: function ({ commit, state }, { item }) {
  axios.put('/secured/projects/' + item.id, item).then((response) => {
    commit('UPDATE_PROJECT', { item: response.data })
  }, (err) => {
    console.log(err)
  })
}
// mutations
UPDATE_PROJECT: (state, { item }) => {
  let idx = state.projects.map(p => p.id).indexOf(item.id)
  state.projects.splice(idx, 1, item)
}

UPDATE_PROJECT 会触发项目列表移除对应的项目并且将服务端返回的数据重新添加到数组中:

app.put('/secured/projects/:id', function (req, res) {
  let project = data.filter(function (p) { return p.id == req.params.id })
  if (project.length > 0) {
    project[0].completed = !project[0].completed
    res.status(201).json(project[0])
  } else {
    res.sendStatus(404)
  }
})

最后一步就是将 completeToggle 组件引入到 projectList 组件中,然后将其添加到列表中:

// new column in table
<td><complete-toggle :item="item" /></td>
// be sure import and add to the components object

整理

基本用途:

  • 将某些data变成组件间公用的状态,组件随时都可以进行访问和响应,解决了props传值的链式响应的代码冗余
  • 给状态配以公用方法,将状态的变更及时响应并处理

基本用法:

/store/store.js

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export const store = new Vuex.Store({
    state: {
        sideBarOpened: false
        //放置公用状态
    },
    getters: {
        changeState: state => {
            //相当于vue实例中的methods,用于处理公用data
            //自vuex 面向组件的数据处理
        }
    },
    mutations: {
        //写法与getters相类似
        //组件想要对于vuex 中的数据进行的处理
        //组件中采用this.$store.commit('方法名') 的方式调用,实现充分解耦
        //内部操作必须在此刻完成(同步)
    },
    actions: {
        //使得mutations能够实现异步调用,实现例如延迟调用
        increment ({ commit }, payload) {
            commit('突变方法名')
            //payload应该是一个对象,可通过模板方法调用传入对象的方式将数据从组件传入vuex
        },
        asyncIncrement({commit}) => {
            setTimeout(() => {
                commit('increment')
            }, 1000)
        }
    },
    modules: {
        //引入某一个state的以上集合的模块,会自动分别填充到上面,使得结构更加清晰
    }
});

main.js

import { store } from './store/store'
*
*
new Vue({
  el: '#app',
  store,    //注入根组件
  ...
})

访问vuex中的数据和方法

this.$store.state.数据名
this.$store.getters.方法名

受影响组件局部定义计算属性响应变化数据

computed: {
     open () {
          return this.$store.state.sideBarOpened
     }
}

将 store 中的 getters/mutations 映射到局部(计算属性/方法)使用mapGetters/mapMutations辅助函数

import { mapGetters } from 'vuex'

computed: {
  // 使用对象展开运算符将 getters 混入 computed 对象中
    ...mapGetters([
        //映射 this.doneTodosCount 为 store.getters.doneTodosCount
      'doneTodosCount',
      //'getter名称',

      // 映射 this.doneCount 为 store.getters.doneTodosCount
        doneCount: 'doneTodosCount'
      // 三个点表示将内部拿出生成键值对,这样使得组件本身的计算属性不受影响
      // 此语法依赖babel-preset-stage-2
    ])
  }

注意事项:

mutation 必须是同步函数 — devtool要保存快照,方便追踪状态变化
使用 v-model 绑定 vuex 计算属性的时候要设置get 和 set 才能双向绑定

computed: {
    value: {
        get () {
            return this.$store.getters.value;
        },
        set (event) {
            this.$store.dispatch('updateValue', event.target.value);
        }
    }
}

注意:

  1. 当 Vue 组件从 store 中读取状态的时候,若 store中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  2. 不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交mutations
// 如果在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

现在,你可以通过 store.state 来获取状态对象,以及通过 store.commit 方法触发状态变更:

store.commit('increment')
console.log(store.state.count) // -> 1

每次对数据对象进行操作的时候,都需要进行commit

state

Vuex 通过 store 选项,提供了一种机制将状态从根组件『注入』到每一个子组件中(需调用 Vue.use(Vuex)):

const app = new Vue({
  el: '#app',
  // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
  store,
  components: { Counter },
  template: `
    <div class="app">
      <counter></counter>
    </div>
  `
})

通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store访问到。让我们更新下 Counter 的实现:

const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}

mapState 辅助函数
辅助函数帮助我们生成计算属性
mapState函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。但是自从有了对象展开运算符(现处于 ECMASCript 提案 stage-3 阶段),我们可以极大地简化写法。

Getters

可以认为是 store 的计算属性

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

会暴露为 store.getters 对象:

store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

也可以接受其他 getters 作为第二个参数
mapGetters 辅助函数
将 store 中的 getters 映射到局部计算属性

import { mapGetters } from 'vuex'
export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getters 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

你想将一个 getter 属性另取一个名字,使用对象形式:

mapGetters({
  // 映射 this.doneCount 为 store.getters.doneTodosCount
  doneCount: 'doneTodosCount'
})
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容