10.VUEX(更新)

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

简单来说:对于多层嵌套的组件使用父子通讯兄弟通讯太繁琐,因此vuex就是把组件共享状态抽取出来以一个全局单例模式管理,把共享的数据函数放进vuex中,任何组件都可以进行使用。

一.安装

npm install vuex@next --save

二.配置 store

vue2

store/index.js  store配置文件

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

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

main.js 入口文件

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

vue3

store/index.js  store配置文件

import { createStore } from 'vuex'

export default createStore({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

main.js 入口文件

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

createApp(App).use(store).use(router).mount('#app')

三.核心概念

vuex有五大核心概念: State  Getter  Mutation   Action   Module 

四.State

提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存,与data相似。

在vuex中state中定义数据,可以在任何组件中进行调用。

  state: {
    name: 'yujiabao',
    age: 18,
    count: 100
  },

访问方式:

方式一:js中访问

{
    this.$store.state.name
}

方式二:标签中访问

<span>{{ $store.state.name }}</span>

方式三:计算属性

  computed: {
    name() {
      return this.$store.state.name;
    },
  },

方式四:mapState辅助函数

方式三如果有多个数据需要映射的话,就需要写很多个计算属性比较麻烦,所以我们可以使用mapState函数。

先按需引入

import { mapState } from "vuex";
把store中的数据映射到计算属性中,mapState函数参数可以接受一个对象,返回值也是一个对象。我们这里直接给计算属性 computed 赋值 mapState 的返回值。
  data() {
    return {
      // 用户名
      userName: "",
      // 密码
      password: "",
    };
  },
  computed: mapState({
    // 键值对形式 表示 this.name 为 this.$store.state.name
    name: "name",
    // 箭头函数形式
    age: (state) => state.age,
    // 普通函数形式,可以使用 this 实例
    count(state) {
      return state.count;
    },
    fenshu: function (state) {
      return this.name + state.count;
    },
  }),

上述写法就相当于

  computed: {  // 这个对象就是 mapState 的返回值
    name() {
      return this.$store.state.name;
    },
    age() {
      return this.$store.state.age;
    },
    count() {
      return this.$store.state.count;
    },
    fenshu() {
      return this.$store.state.name + this.$store.state.count;
    },
  },

把这个对象赋值给computed不就和原始的写法一样了吗,所以mapState起到了简化的作用。 

但是这样直接给计算属性 computed 赋值的话,那么computed中就只有对vuex状态的获取,而不能写别的计算属性了,所以我们使用对象的扩展运算符。

(其实也可以写,直接通过this获取当前组件数据就行,但是总觉得不太规范)

  data() {
    return {
      // 用户名
      userName: "",
      // 密码
      password: "",
    };
  },
  computed: mapState({
    name: "name",
    age: (state) => state.age,
    count(state) {
      return state.count;
    },
    fenshu: function (state) {
      return this.name + state.count;
    },
    // 与 Vuex 数据无关的变量,也能写在mapState里面,但是总觉得不太规范
    user() { 
      return this.userName
    }
  }),

还是使用对象的扩展运算符。 

  data() {
    return {
      // 用户名
      userName: "",
      // 密码
      password: "",
    };
  },
  computed: {
    ...mapState({
      name: "name",
      age: (state) => state.age,
      count(state) {
        return state.count;
      },
      fenshu: function (state) {
        return this.name + state.count;
      },
    }),
    user() {
      return this.userName;
    },
  },
mapState 函数参数也可以接受一个数组

当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。

  computed: {
    ...mapState([
      // // 映射 this.name 为 store.state.name
      "name",
      "age",
      "count",
    ]),
    user() {
      return this.userName;
    },
  },

就相当于

  computed: {
    ...mapState({
      name: "name",
      age: "age",
      count: "count",
    }),
    user() {
      return this.userName;
    },
  },

最终演化为

  computed: {
    name() {
      return this.$store.state.name;
    },
    age() {
      return this.$store.state.age;
    },
    count() {
      return this.$store.state.count;
    },
    user() {
      return this.userName;
    },
  },
计算属性里可以有多个 mapState 函数
  computed: {
     ...mapState([
      "name",
      "age",
      "count",
    ]),
    ...mapState({
      count(state) {
        return state.count;
      },
    }),
    user() {
      return this.userName;
    },
  },

五.Getter 

有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数:

computed: {
  doneTodosCount () {
    return this.$store.state.todos.filter(todo => todo.done).length
  }
}

如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。

注意:从 Vue 3.0 开始,getter 的结果不再像计算属性一样会被缓存起来。这是一个已知的问题,将会在 3.1 版本中修复。

Getter 接受 state 作为其第一个参数:

可以通过调用该参数取访问 vuex.state 的数据

 state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos (state) {
      return state.todos.filter(todo => todo.done) // -> [{ id: 1, text: '...', done: true }]
    }
  }

Getter 也可以接受其他 getters 作为第二个参数:

可以通过调用该参数取访问 vuex.gettets 的数据

 state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos (state) {
      return state.todos.filter(todo => todo.done)  // -> [{ id: 1, text: '...', done: true }]
    },
    doneTodosCount (state, getters) {
      return getters.doneTodos.length  // -> 1
    } 
  }

访问方式:

方式一:js中访问

{
    this.$store.getter.doneTodos 
}

方式二:标签中访问

<span>{{ $store.state.doneTodos }}</span>

 方式三:计算属性

computed: {
  doneTodosCount () {
    return this.$store.getters.doneTodosCount
  }
}

方式四:通过方法访问

你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。

getters: {
  // ...
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}

 其实就相当于是这样:getTodoById 返回一个函数,我们访问 getTodoById 的时候可以像方法一样传个参数过来。

getters: {
  // ...
   getTodoById: (state) => {
     return (id) => {
       return state.todos.find(todo => todo.id === id)
     }
   }
}

 调用的时候传个参数:

store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }

方式五:mapGetters 辅助函数(同 state)

六.Mutation   

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。

Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:

  state: {
    age: 18,
  },
  mutations: {
   addAge(state) {
     state.age++
   },
  },

你不能直接调用一个 mutation 处理函数。这个选项更像是事件注册:“当触发一个类型为 addAge的 mutation 时,调用此函数。”要唤醒一个 mutation 处理函数,你需要以相应的 type 调用 store.commit 方法:

   this.$store.commit('addAge')

你可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload)

  mutations: {
    addAge(state) {
      state.age++
    },
    delAge(state, num) {
      state.age = state.age - num
    },
  },
   this.$store.commit("delAge", 10);

在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:

  mutations: {
    addAge(state) {
      state.age++
    },
    delAge(state, payload) {
      state.age = state.age - payload.num
    },
  },
      this.$store.commit("delAge", {
        num: 10,
      });

对象风格的提交方式 

提交 mutation 的另一种方式是直接使用包含 type 属性的对象:

      this.$store.commit({
        type: "delAge",
        num: 10,
      });

当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此处理函数保持不变: 

  mutations: {
    addAge(state) {
      state.age++
    },
    delAge(state, payload) {
      state.age = state.age - payload.num
    },
  },

通过辅助函数mapMutations 触发 ,基本上就类似于 State,不过一个是在computed中,一个在methods中

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
}
<button @click="increment"></button>
<button @click="incrementBy(100)"></button>
<button @click="add"></button>

使用常量替代 Mutation 事件类型(不重要)

使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// index.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
  state: { ... },
  mutations: {
    // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
    [SOME_MUTATION] (state) {
      // mutate state
    }
  }
})

Mutation 必须是同步函数

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {  // 异步,不可取
      state.count++
    })
  }
}

Mutation 中的回调函数必须是同步的,这是因为在 Vuex 中,状态的变化必须是可追踪且同步的。如果需要执行异步操作,应该使用 Action。

七.Action   

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。

  • Action 可以包含任意异步操作。

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

实践中,我们会经常用到 ES2015 的 参数解构 (opens new window)来简化代码:

actions: {
  increment ({ commit }) {
    commit('increment')
  }
}

Action 通过 store.dispatch 方法触发:

this.$store.dispatch('increment')

也像Mutation函数那样可以额外接受一个参数,即载荷:

this.$store.dispatch('increment', 10)
this.$store.dispatch('increment', {num: 10})
actions: {
  increment ({ commit },payload) {
    commit('increment')
  }
}


也像Mutation函数那样可以用对象风格的提交方式:

this.$store.dispatch({
  type: 'increment',
  amount: 10
})

可以用辅助函数mapActions 触发,参考Mutation

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

      // `mapActions` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
    })
  }
}
<button @click="increment"></button>
<button @click="incrementBy(100)"></button>
<button @click="add"></button>

乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作: 

actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

来看一个更加实际的购物车示例,涉及到调用异步 API 和分发多重 mutation:

actions: {
  checkout ({ commit, state }, products) {
    // 把当前购物车的物品备份起来
    const savedCartItems = [...state.cart.added]
    // 发出结账请求
    // 然后乐观地清空购物车
    commit(types.CHECKOUT_REQUEST)
    // 购物 API 接受一个成功回调和一个失败回调
    shop.buyProducts(
      products,
      // 成功操作
      () => commit(types.CHECKOUT_SUCCESS),
      // 失败操作
      () => commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
  }
}

注意我们正在进行一系列的异步操作,并且通过提交 mutation 来记录 action 产生的副作用(即状态变更)。

八.Module

Vuex允许我们将store分割成模块(Module), 而每个模块拥有自己的state、getters、mutation、action等,甚至是嵌套子模块——从上至下进行同样方式的分割。

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值