首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

vuex

写在前面

​ Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

使用Vue开发项目时,通常我们就会遇到如下几种棘手的问题:

问题1:通过路由传递参数,我们会采用params或者query形式,但这两种方式都会在URL上做手脚,如果传递的参数过多,会导致400 Bad Request(如,点击表格某行,携带行数据跳转到新页面进行查看)。 问题2:兄弟组件传值 问题3:多处地方使用同一数据,为节省重复请求(最为常见)

直接上业务场景

​ 从A页面携带下钻参数(ID)到B页面,然后B页面获取参数(ID)进行数据请求。由于要下钻的ID过长,受浏览器的URL长度限制问题,我们不能采用常规的prams(xxx.com/B/:ID)或query(xxx.com/B?ID=…)形式传递。可以使用Vuex做中间过渡,跳转前存储ID信息,进入B页面后从Vuex获取ID信息。 ​ 如果用户在B页面刷新数据,则Vuex的ID状态值会被清空无法获取,这里只能借助localStorage进行持久化进行处理(当然,如果直接使用localstorage进行持久化存储,而不借助Vuex也是可行的)

Vuex插件,每次调用mutation之后向localstorage存值,防止刷新丢失

注意,向vuex中存值this.$store.commit('sem/${OPT_ORG_LOG_ID}', id)我们规定凡是以$$结尾的mutation type便存储到localstorage中,其余不做处理!

代码语言:javascript
复制
const identifier = '$$'

export default function (store) {
  // 当 store 初始化后调用
  store.subscribe((mutation, state) => {
    // 每次 mutation 之后调用
    // mutation 的格式为 { type, payload }
    let {type, payload} = mutation
    if (type.endsWith(identifier)) {
      window.localStorage.setItem(type, JSON.stringify(payload))
    }
  })
}

Vuex模块,调用时需要追加模块名称

注意,this.id = this.$store.getters['sem/${OPT_ORG_LOG_ID}']首先从store中获取,如果store中不存在则从localstorage中获取(刷新)

代码语言:javascript
复制
import {OPT_ORG_LOG_ID} from '../type'

export default {
  namespaced: true,
  state () {
    return {
      // @deprecated
      orgLogID: ''
    }
  },
  mutations: {
    [OPT_ORG_LOG_ID] (state, orgLogID) {
      state.orgLogID = orgLogID
    }
  },
  getters: {
    [OPT_ORG_LOG_ID] (state) {
      return state.orgLogID 
        || JSON.parse(window.localStorage.getItem(`sem/${OPT_ORG_LOG_ID}`))
    }
  }
}

状态管理模式

“单向数据流”理念的极简示意:

  • state,驱动应用的数据源;
  • view,以声明方式将 state 映射到视图;
  • actions,响应在 view 上的用户输入导致的状态变化。

我们在开发中会遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏。

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。

对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件(非父子组件)间的状态传递无能为力;对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。

Vuex简介

Vuex 和单纯的全局对象有以下两点不同:

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  • 不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
代码语言:javascript
复制
Vue.use(Vuex);
const store = new Vuex.Store({
  // 数据状态
  state {...},
  // 更改状态 store.commit
  mutations: {...},
  // 类似于mutation(不能直接变更状态,可以异步操作) store.dispatch
  actions: {...},
  // 派生状态(如,过滤、计数)
  getters: {...}
})

// 将状态从根组件“注入”到每一个子组件中,且子组件能通过 this.$store 访问到。
const app = new Vue({
  el: '#app',
  store,
  data() {}
});

State

Vuex 使用单一状态树,这可以让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。需要注意,单状态树和模块化并不冲突!

由于 store 中的状态是响应式的,在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。

代码语言:javascript
复制
computed: {
  count () {
    return store.state.count // this.$store.state.count
  }
}

mapState 辅助函数

当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性。

代码语言:javascript
复制
import { mapState } from 'vuex'

computed: mapState({
  // 映射 this.count 为 store.state.count
  'count',

  // 箭头函数可使代码更简练
  count: state => state.count,

  // 传字符串参数 'count' 等同于 `state => state.count`
  countAlias: 'count',

  // 为了能够使用 `this` 获取局部状态,必须使用常规函数
  countPlusLocalState (state) {
    return state.count + this.localCount
  },

  // 使用对象展开运算符将此对象混入到外部对象中
  ...mapState({
    // ...
  })
})

Getter

Getter(state, getters)可以从 store 中的 state 中派生出一些状态(如,对数据进行过滤操作)。对于多个组件需要用同一属性时,意义重大!类似于计算属性,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。完整请参照 https://vuex.vuejs.org/zh-cn/getters.html

Mutation

mutation 必须是同步函数!!!

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。调用 store.commit(type, payload) 方法来触发mutations中的相关方法。

代码语言:javascript
复制
// ...
mutations: {
  increment (state, n) {
    state.count += n
  }
}

store.commit('increment', 10)

注意:Mutation 需遵守 Vue 的响应规则

  • 最好提前在你的 store 中初始化好所有所需属性
  • 当需要在对象上添加新属性时,你应该
    • 使用 Vue.set(obj, 'newProp', 123), 或者
    • 以新对象替换老对象。例如,利用 stage-3 的对象展开运算符我们可以这样写:

    state.obj = { ...state.obj, newProp: 123 }

完整请参照:https://vuex.vuejs.org/zh-cn/mutations.html

Action

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。
  • 通过 store.dispatch 方法触发

组合 Action

store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise。

代码语言:javascript
复制
actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

现在你可以:

代码语言:javascript
复制
store.dispatch('actionA').then(() => {
  // ...
})

在另外一个 action 中也可以:

代码语言:javascript
复制
// 假设 getData() 和 getOtherData() 返回的是 Promise

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}

​ 一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

完整请参照:https://vuex.vuejs.org/zh-cn/actions.html

Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为命名空间模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。

插件

Vuex 的 store 接受 plugins 选项,这个选项暴露出每次 mutation 的钩子。Vuex 插件就是一个函数,它接收 store 作为唯一参数。

代码语言:javascript
复制
const myPlugin = store => {
  // 当 store 初始化后调用
  store.subscribe((mutation, state) => {
    // 每次 mutation 之后调用
    // mutation 的格式为 { type, payload }
  })
}

然后像这样使用:

代码语言:javascript
复制
const store = new Vuex.Store({
  // ...
  plugins: [myPlugin]
})

示例:项目中我们会使用plugin来初始化一些数据

代码语言:javascript
复制
const initActionList = [
  'base/' + INIT_BUSINESS_SYSTEM_LIST,
  'threat/' + INIT_STD_COEFFICIENT_LIST
]
export default function (store) {
  for (let action of initActionList) {
    Bus.$once(action, () => {
      store.dispatch(action)
    })
  }
}

但是 ,使用plugin的Bus.$once去初始化请求,而不再每个使用的模块自身dispatch。会有解决不掉的两个问题:

  • 点击某个按钮触发相关数据($once只适合初始化时请求)
  • 某请求依赖store中的情况(刷新)await dispatch('actionA') // 等待 actionA 完成

表单处理

当在严格模式中使用 Vuex 时,在属于 Vuex 的 state 上使用 v-model 会比较棘手

代码语言:javascript
复制
<input v-model="obj.message">

在用户输入时,v-model 会试图直接修改 obj.message。在严格模式中,由于这个修改不是在 mutation 函数中执行的, 这里会抛出一个错误。

使用传统的value+input事件实现,但是比较啰嗦。

代码语言:javascript
复制
<input :value="message" @input="updateMessage">
代码语言:javascript
复制
computed: {
  ...mapState({
    message: state => state.obj.message
  })
},
methods: {
  updateMessage (e) {
    this.$store.commit('updateMessage', e.target.value)
  }
}

这里,可以使用双向绑定的计算属性:

代码语言:javascript
复制
computed: {
  message: {
    get () {
      return this.$store.state.obj.message
    },
    set (value) {
      this.$store.commit('updateMessage', value)
    }
  }
}

总结

使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。

下一篇
举报
领券