Vue系列入门教程(15)-vuex核心内容

Web前端 潘老师 1个月前 (09-12) 362 ℃ (4) 扫码查看

本文基于上一篇文章:Vue系列入门教程(14)-vuex入门案例
VueX中的核心成员列表如下:

  • state: 存放状态,类似于vue组件中的data
  • mutations:操作state成员,必须是同步方法,类似于vue组件中的methods
  • getters: 加工state成员给外界,类似于vue组件中的computed
  • actions: 异步操作
  • modules: 模块化状态管理

state的使用在上篇文章演示过了,存储在 Vuex 中的数据和 Vue 实例中的 data 遵循相同的规则,可以通过this.$store在所有组件中获取。

有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数,不适应vuex情况下,我们可能会用计算属性:

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

如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

案例1:通过属性访问

1、将上篇文章中的vuex-app项目中store下的index.js修改如下:

import Vue from 'vue'
// 导入vuex
import Vuex from 'vuex'
//挂载Vuex
Vue.use(Vuex)

//创建Vuex对象
const store = new Vuex.Store({
    state:{
        products:[
            {name:"小米手机",price:"5000",country:"CN"},
            {name:"苹果手机",price:"6000",country:"US"},
            {name:"魅族手机",price:"4000",country:"CN"},
            {name:"Vivo手机",price:"3000",country:"CN"}
        ]
    },
    getters:{
        // Getter 接受 state 作为其第一个参数,过滤出中国品牌手机:
        cnProducts:state => {
            return state.products.filter(product => product.country == 'CN');
        }
    }
})
// 默认导出store
export default store

2、在components目录下,新建Product.vue:

<template>
    <div>
        <ul>
            <li v-for="(item,i) in cnProducts" :key="i">
                商品名:{{item.name}},价格:{{item.price}},国家:{{item.country}}
            </li>
        </ul>
    </div>
</template>
 
<script>
    export default {
        computed:{
            cnProducts(){
                // Getter 会暴露为 $store.getters 对象,可以以属性的形式访问这些值:
                return this.$store.getters.cnProducts;
            }
        }
    }
</script>
 
<style scoped>
 
</style>

Getter 会暴露为 $store.getters 对象,可以以属性的形式访问这些值,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的

3、修改App.vue如下:

<template>
  <div id="app">
        <Product></Product>
  </div>
</template>

<script>
import Product from './components/Product.vue'

export default {
  name: 'app',
  components: {
        Product
  }
}
</script>

<style>
</style>

4、运行测试:
Vue系列入门教程(15)-vuex核心内容

案例2:通过方法访问

你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。
1、修改store\index.jsStore对象如下:

//创建Vuex对象
const store = new Vuex.Store({
        state:{
            products:[
                {name:"小米手机",price:"5000",country:"CN"},
                {name:"苹果手机",price:"6000",country:"US"},
                {name:"魅族手机",price:"4000",country:"CN"},
                {name:"Vivo手机",price:"3000",country:"CN"}
            ]
        },
        getters:{
            // Getter 接受 state 作为其第一个参数,过滤出中国品牌手机:
            getProductByCountry:state =>(country)=> {
                return state.products.filter(product => product.country == country);
            }
        }
})

2、修改Product.vue的computed如下:

computed:{
    cnProducts(){
        // Getter 会暴露为 $store.getters 对象,可以以方法的形式访问这些值:
        return this.$store.getters.getProductByCountry("CN");
    }
}

3、测试结果还是一样

注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。

1、mutations是操作state数据的方法的集合,比如对该数据的修改、增加、删除等等。我 们之前说过在组件中不能直接修改state,更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。

2、mutations方法都有默认的形参:([state] [,payload])

  • state是当前VueX对象中的state
  • payload是该方法在被调用时传递参数使用的

案例

1)将小米手机名称改为华为手机
修改store\index.jsStore如下:

//创建Vuex对象
const store = new Vuex.Store({
        state:{
            products:[
                {name:"小米手机",price:"5000",country:"CN"},
                {name:"苹果手机",price:"6000",country:"US"},
                {name:"魅族手机",price:"4000",country:"CN"},
                {name:"Vivo手机",price:"3000",country:"CN"}
            ]
        },
        getters:{
            // Getter 接受 state 作为其第一个参数,过滤出中国品牌手机:
            getProductByCountry:state =>(country)=> {
                return state.products.filter(product => product.country == country);
            }
        },
        mutations:{
            // 将小米手机更名为华为手机
            change(state){
                state.products[0].name="华为手机";
            }
        }
})

2)将Product.vue修改如下:

<template>
    <div>
        <ul>
            <li v-for="(item,i) in cnProducts" :key="i">
                商品名:{{item.name}},价格:{{item.price}},国家:{{item.country}}
            </li>
        </ul>
        <button @click="handleClick">更改</button>
    </div>
</template>
 
<script>
    export default {
        methods:{
            handleClick(){
                // 通过$store.commit调用mutations中方法
                this.$store.commit("change");
            }
        },
        computed:{
            cnProducts(){
                // Getter 会暴露为 $store.getters 对象,可以以属性的形式访问这些值:
                return this.$store.getters.getProductByCountry("CN");
            }
        }
    }
</script>
 
<style scoped>
 
</style>

3)测试点击按钮:
Vue系列入门教程(15)-vuex核心内容
4)在实际生产过程中,会遇到需要在提交某个mutations时需要携带一些参数给方法使用,即 mutation 的 载荷(payload)。
a)单个值提交时:
修改store\index.jsmutations如下:

mutations:{
    // 将小米手机更名为某手机
    change(state,playload){
        state.products[0].name=playload;
    }
}

修改Product.vuehandleClick如下:

handleClick(){
    // 通过$store.commit调用mutations中方法,传递单个参数
    this.$store.commit("change","OPPO手机");
}

测试:
Vue系列入门教程(15)-vuex核心内容
b)多个值组装成对象提交:
修改store\index.jsmutations如下:

mutations:{
    // 将小米手机更名为某手机
    change(state,playload){
        // state.products[0] = playload;这样写页面不会跟着变
        state.products.splice(0,1,playload);
    }
}

修改Product.vuehandleClick如下:

handleClick(){
    // 通过$store.commit调用mutations中方法,提交对象
    this.$store.commit("change",{name:"OPPO手机",price:7000,country:"CN"});
}

测试:
Vue系列入门教程(15)-vuex核心内容
c)另一种提交方式
修改Product.vuehandleClick如下:

handleClick(){
    // 通过$store.commit调用mutations中方法,提交对象
    this.$store.commit({
        type:"change",
        phone:{name:"OPPO手机",price:7000,country:"CN"}
    });
}

修改store\index.jsmutations如下:

mutations:{
    // 将小米手机更名为某手机
    change(state,playload){
        // playload接收的commit提交的整个对象,type和phone都是其中属性
        state.products.splice(0,1,playload.phone);
    }
}

测试效果一样。
5)Mutation 需遵守 Vue 的响应规则
为了配合Vue的响应式数据,我们在Mutations的方法中,应当使用Vue提供的方法来进行操作。如果使用delete或者xx.xx = xx的形式去删或增,则Vue不能对数据进行实时响应。
a)Vue.set 为某个对象设置成员的值,若不存在则新增
例如对state对象中添加一个title成员:
store\index.jsmutations新增如下方法:

// 给state添加属性
addTitle(state){
    Vue.set(state,"title","手机列表");
}

修改Product.vuetemplate如下:

<template>
    <div>
        <h1>{{$store.state.title}}</h1>
        <ul>
            <li v-for="(item,i) in cnProducts" :key="i">
                商品名:{{item.name}},价格:{{item.price}},国家:{{item.country}}
            </li>
        </ul>
        <button @click="handleClick">更改</button>
    </div>
</template>

修改Product.vuehandleClick如下:

handleClick(){
    // 通过$store.commit调用mutations中方法,添加属性
    this.$store.commit("addTitle");
}

测试:
Vue系列入门教程(15)-vuex核心内容
b)Vue.delete 删除成员,
例如删除state对象中的title成员:Vue.delete(state,“title”);在此就不做演示了。

由于直接在mutation方法中进行异步操作,将会引起数据失效。所以提供了Actions来专门进行异步操作,最终提交mutation方法。
Actions中的方法有两个默认参数:

  • context 上下文(相当于箭头函数中的this)对象
  • payload 挂载参数

我们在第三大点中案例中第4)小点c部分将小米手机改为OPPO手机,现在们希望点击更改按钮后果两秒再去执行change方法,由于setTimeout是异步操作,所以需要使用actions
修改store\index.jsmutationsactions方法如下:

mutations:{
    change(state,playload){
        state.products.splice(0,1,playload.phone);
    }
}
actions:{
    // 两秒后提交change
    asyncChange(context,palyload){
        // playload接收的使整个对象
        setTimeout(()=>{
            context.commit("change",palyload);
        },2000);
    }
}

在组件中调用,修改Product.vuehandleClick方法如下:

handleClick(){
    // 通过$store.dispatch调用actions中方法
    this.$store.dispatch({
        type:"asyncChange",
        phone:{name:"OPPO手机",price:7000,country:"CN"}
    });
}

测试发现点击按钮,2秒后成功更改。

1、当应用变得非常复杂时,store 对象就有可能变得相当臃肿。可以采用模块化管理模式。Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 statemutationsactionsgetters、甚至是嵌套子模块——从上至下进行同样方式的分割。

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

2、组件内调用模块a的状态:

this.$store.state.a

commit提交或者dispatch某个方法和以前一样,会自动执行所有模块内的对应type的方法:

this.$store.commit('change')
this.$store.dispatch('asyncChange')

3、模块的局部状态
1)对于模块内部的 mutationgetter,接收的第一个参数是模块的局部状态对象。

const moduleA = {
  state: () => ({
    count: 0
  }),
  mutations: {
    increment (state) {
      // 这里的 `state` 对象是模块的局部状态
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}

2)同样,对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState

const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

3)对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:

const moduleA = {
  // ...
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}

Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:

  • 应用层级的状态应该集中到单个 store 对象中。
  • 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
  • 异步逻辑都应该封装到 action 里面。

只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。

对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:
Vue系列入门教程(15)-vuex核心内容
请参考购物车示例
但也有人习惯store中目录结构如下:

store:.
│  actions.js
│  getters.js
│  index.js
│  mutations.js
│  mutations_type.js   ##该项为存放mutaions方法常量的文件,按需要可加入
│
└─modules
        Astore.js

对应的内容存放在对应的文件中,和以前一样,在index.js中存放并导出storestate中的数据尽量放在index.js中。而modules中的Astore局部模块状态如果多的话也可以进行细分。


版权声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系潘老师进行处理。
喜欢 (6)
请潘老师喝杯Coffee吧!】
分享 (0)
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填【保密】)
  • 网址
(4)个小伙伴在吐槽
  1. 模块内部的getters有三个参数(state,getters,rootState),第二个参数是什么?rootstate是根节点的状态还是父节点的状态?如果是根节点的状态,那怎么取到父节点的状态?
    Que Sera Sera2020-09-16 19:37 回复
    • 潘老师
      state是模块私有的状态,getters是全局的getters,rootState是根节点状态,我不明白你说的父节点是指的什么?是子节点再去分成modules吗?
      潘老师2020-09-17 08:53 回复