Vuex五大核心概念详细篇,以及代码演示



.Vuex核心概念

一、State单一状态树

提供一个响应式数据,存贮公共数据的地方

  Vuex提出使用单—状态树,什么是单—状态树呢?

  英文名称是Single Source of Truth,也可以翻译成单—数据源。

  即用一个对象就包含了全部的状态数据。也就是说如果我们定义了一个 store 的实例,那么这个 store 实例里面只有一个 state。state作为构造器选项,定义了所有我们需要的基本状态参数。

这个和我们在应用开发中比较类似︰

  如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。所以Vuex也使用了单—状态树来管理应用层级的全部状态。

  单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。

const store = new Vuex.Store({
    state: {
        // 提供一个响应式数据,存贮公共数据的地方
        counter: 100
    }
})

二、Getter

  从基本数据(state)派生的数据,相当于state的计算属性
组件:
<template>
  <div id="app">
    <h1>---------- $store.getters -----</h1>
    <h2>counter平方:{{ $store.getters.powerCounter }}</h2>
    <h2>及格学生名单:{{ $store.getters.passStudents }}</h2>
    <h2>及格人数:{{ $store.getters.passStudentsLength }}</h2>
    <h2>动态获取:{{ $store.getters.dynamicStudents(50) }}</h2>
  </div>
</template>
效果:
counter平方:10000
及格学生名单:[ "张三", "李四", "王五", "赵柳" ]
及格人数:4
动态获取:[ "张三", "李四", "王五", "赵柳", "陈琦" ]
getters:
const store = new Vuex.Store({
    state: {
        // 提供一个响应式数据,存贮公共数据的地方
        counter: 100,
        students: [
            { name: "张三", age: 18, fraction: 99 },
            { name: "李四", age: 23, fraction: 82 },
            { name: "王五", age: 22, fraction: 76 },
            { name: "赵柳", age: 19, fraction: 65 },
            { name: "陈琦", age: 25, fraction: 55 },
        ]
    },
    getters: {
        // 从基本数据(state)派生的数据,相当于state的计算属性
        powerCounter(state) {
            return Math.pow(state.counter, 2)
        },
        passStudents(state) {
            // 需求:获取及格的人名集合
            let passArray = []
            state.students.filter(item => item.fraction >= 60).map(val => passArray.push(val.name))
            console.log(state.students);
            return passArray
        },
        passStudentsLength(state, getters) {
            // 需求:获取及格的人有多个
            // 传入第二个参数getters
            return getters.passStudents.length
        },
        dynamicStudents(state) {
            // ****计算属性传参****
            // 需求:动态获取分数线上的人有多个,传入50,就返回50分以上有哪些人
            // 注意:这里不能直接向此计算属性传入第二个参数fraction了,如dynamicStudents(state,fraction),因为默认第二个参数是getters
            // 需要向此属性传入参数,那么就返回一个函数给它,让return的函数去接受那个fraction参数
            return (fraction) => {
                let passArray = []
                state.students.filter(item => item.fraction >= fraction).map(val => passArray.push(val.name))
                return passArray
            }
        }
    },
})

三、Mutation状态更新

1)更改state方法;

  Vuex的store状态的更新唯一方式:提交Mutation,其主要包括两部分∶

  字符串的事件类型( type )、一个回调函数( handler ),该回调函数的第一个参数就是state。

2)定义mutation的方式:
const store = new Vuex.Store({
     state: {
        counter: 100,
    },
    mutations: {
        increment(state) { // 变更状态,更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
            state.counter++
        },
        decrement(state) { 
            state.counter--
        }
    },
})   
3)通过mutation提交更新
<script>
    export default {
        name: "App",
        methods: {
            addition() {
                this.$store.commit("increment");
            },
            subtraction() {
                this.$store.commit("decrement");
            }
        }
    };
</script>
4)mutation在提交时如何传传递参数,以及传递多个参数?

  在通过mutation更新数据的时候,有可能我们希望携带一些额外的参数参数被称为是mutation的载荷(Payload)

  我们通常会以对象的形式传递,也就是payload是一个对象.这个时候可以再从对象中取出相关的信息.

4.1)传一个参数的写法(官网例子):
// ...
mutations: {
  increment (state, n) {
    state.count += n
  }
}
store.commit('increment', 10)
4.2)对象风格的提交方式,传多个参数的写法:

  此时参数不能继续在后面加,后面的参数无效,传进去的参数为undefined;

  官网的解释:In most cases, the payload should be an object so that it can contain multiple fields, and the recorded mutation will also be more descriptive;在大多数情况下,有效载荷应该是一个对象,以便它可以包含多个字段,并且记录的变异也将更具描述性.

  所以,我们可以将参数以对象的方式传进去,多个属性就是多个参数了。

// ...
mutations: {
    incrementCount(state, payload) {
        console.log('payload: ', payload);
        // payload:  {type: 'incrementCount', count: 5, age: 18, name: 'chenxx'}
        state.counter += payload.count
    }
}
// ...
 addCount(count) {
      // 1、普通的提交封装
      this.$store.commit("incrementCount", count);

      // 2、特殊的提交封装(传递多个参数)
      this.$store.commit({
        type: "incrementCount",
        count,
        age:18,
        name:"chenxx"
      });
    },

links:参考文章

5)Mutation响应规则

  Vuex的store中的state是响应式的,当state中的数据发生改变时,Vue组件会自动更新.这就要求我们必须遵守一些Vuex对应的规则:

  提前在store中初始化好所需的属性.

  当给state中的对象添加新属性时,使用下面的方式:

mutations: {
    modifyMsg(state) {
        //修改信息
        state.info.name = "info陈"//---响应式

        //新增
        state.info["sex"] = "man"//---不能做到响应式
        Vue.set(state.info, "sex", "man");//---Vue.set方法,响应式

        // 删除
        delete state.info.age //---不能做到响应式
        Vue.delete(state.info, "age");//---Vue.delete,响应式
    },
}
6)使用常量替代 Mutation 事件类型

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

  Vuex中为了代码看起来简洁清晰,常常使用常量替代Mutation事件类型

mutation-types.js
export const INCREMENT = "increment";
export const DECREMENT = "decrement";
export const INCREMENTCOUNT = "incrementCount";
export const INCREMENTSTUDENT = "incrementStudent";
export const MODIFYMSG = "modifyMsg";

store.js
import * as mutationTypes from './mutation-types' //导入模块,并给以别名mutationTypes

const store = new Vuex.Store({
    mutations: {
        // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
        [mutationTypes.INCREMENT](state) {
            state.counter++
        },
        [mutationTypes.DECREMENT](state) {
            state.counter--
        }
    }
})
App.vue
<script>
    import * as mutationTypes from './store/mutation-types'
    export default {
        name: "App",
        methods: {
            addition() {
                this.$store.commit(mutationTypes.INCREMENT);
            },
            subtraction() {
                this.$store.commit(mutationTypes.DECREMENT);
            }
            // mutationTypes.INCREMENT是个常量键,
            // 不能把它作为字符串,不然它会取不到mutation-types.js中定义的INCREMENT的常量值。
        }
    };
</script>

  用不用常量取决于你——在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做。

link:参考文章

7)Mutation 必须是同步函数

  一条重要的原则就是要记住 mutation 必须是同步函数。为什么?

  通常情况下, Vuex要求我们Mutation中的方法必须是同步方法.主要的原因是当我们使用devtools时,可以devtools可以帮助我们捕捉mutation的快照.但是如果是异步操作,那么devtools将不能很好的追踪这个操作什么时候会被完成.

mutations: {
    setTimeout(() => {
        state.counter++
    }, 1000)
    // devtools将无法跟踪counter的状态变化
}

四、Action

1)放的是异步的操作 通过dispatch的方法让action里面的方法执行

Action 类似于 mutation,不同在于:

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

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

    context:上下文↓,这里得上下文是store:

    https://www.cnblogs.com/echolun/p/11438363.html

2)最简单的方式
const store = new Vuex.Store({
    actions: {        
        infoUpdateActions(context, payload) {
            setTimeout(() => {
                context.commit("modifyMsg")
                console.log('payload: ', payload);
            }, 1000);
        },       
    })
<script>
    export default {
        methods: {   
            modifyMsgActions() {
                // 最简易
                this.$store.dispatch("infoUpdateActions", "payload---Obj");              
            },
        },
    };
</script>
3)使用payload对象形式
const store = new Vuex.Store({
    actions: {
        infoUpdateActions(context,payload) {    
            // payload对象形式
            setTimeout(() => {
                context.commit("modifyMsg")
                console.log('payload: ', payload.message);
                payload.success()
            }, 1000);
        },       
    })
<script>
    export default {
        methods: {   
            modifyMsgActions() {
                // payload对象形式
                this.$store.dispatch("infoUpdateActions", {
                    message: "payload---Obj",
                    success() {
                        console.log("commit-over1");
                    },
                });
            }
        }
    };
</script>
4)使用Promise .then
const store = new Vuex.Store({
    actions: {
        infoUpdateActions(context, payload) {
            // Promise 
            return new Promise((resolve) => {
                setTimeout(() => {
                    context.commit("modifyMsg")
                    console.log('payload: ', payload);
                    resolve("commit-over")
                }, 1000);
            })
        },
    })
<script>
    export default { 
        methods: {   
            modifyMsgActions() {                
                //  Promise 
                this.$store
                    .dispatch("infoUpdateActions", "payload-Object")
                    .then((data) => {
                    console.log(data);
                });
            },
        },
    };
</script>

五、Module

模块化vuex

  可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

  由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}
const store = new Vuex.Store({
    modules: {
        moduleA
    }
})

store.state.moduleA // -> moduleA 的状态
1) moduleA
state、getters、
const moduleA = {
    // 对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
    state: () => ({
        message: "Message"
        // 箭头函数不是()=>{}吗,这个怎么多了个()??
        // 这样写实为了给箭头函数返回JSON对象,如果不加(),那么{}中的内容将会被当做代码,这样js语法错误。
        // <h2>modules:{{ $store.state.moduleA.message }}</h2>,在使用时有moduleA的前缀
    }),   
    getters: {
        // modules-getters带一个参数:state
        updateMsgModule1(state) {
            return state.message + "参数:__state"
        },
        // modules-getters带两个参数:state与moduleA的getters
        updateMsgModule2(state, getters) {
            return getters.updateMsgModule1 + "__getters"
        },
        // modules-getters带三个参数:state、getters和roteState(根的state)
        updateMsgModule3(state, getters, roteState) {
            return state.message + "参数:__roteState" + roteState.counter
        },
    },
}
mutations、actions
const moduleA = {
    mutations: {
        // modules的mutations使用与root的一样
        moudleChangeMsg(state, payload) {
            state.message = payload
        }
    },

    actions: {
        // context 此处上下文指moduleA,使用与root一致,只不过上下文指代不同
        // actions写法1:
        moduleAactionsTest(context, payload) {
            console.log(context);
            return new Promise((resolve) => {
                setTimeout(() => {
                    context.commit("moudleChangeMsg", payload)
                    resolve("moduleAactions--over")
                }, 1000);
            })
        },
        // actions写法2:对象的解构赋值context--->>>state、commit、rootState
        moduleAactionsTest ({ state, commit, rootState }) {
            if ((state.count + rootState.count) % 2 === 1) {
                commit('increment')
            }
        }

    },    
    modules: {
        // 又可以往下嵌套....
    },
}


2) App
<template>
<div id="app">
    <h1>---------- $store.modules -----</h1>
    <h2>modules:{{ $store.state.moduleA.message }}</h2>

    <button @click="modifyModuleAMsg">修改moduleA信息</button>
    <button @click="asyncModifyModuleAMsg">异步修改moduleA信息</button>

    <h2>moduleAgetters1————{{ $store.getters.updateMsgModule1 }}</h2>
    <h2>moduleAgetters2————{{ $store.getters.updateMsgModule2 }}</h2>
    <h2>moduleAgetters3————{{ $store.getters.updateMsgModule3 }}</h2>

    </div>
</template>
<script>
    export default {
        name: "App",
        methods: {
            // modules↓↓↓↓
            modifyModuleAMsg() {
                this.$store.commit("moudleChangeMsg", "  payloadIns");
            },
            asyncModifyModuleAMsg() {
                this.$store
                    .dispatch("moduleAactionsTest", "  payloadIns")
                    .then((data) => {
                    console.log(data);
                });
            },
        },
    };
</script>
6.项目结构:Vuex下index的模块分离

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

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

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

  对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:

└── store
    ├── index.js          # 我们组装模块并导出 store 的地方
    ├── actions.js        # 根级别的 action
    ├── getters.js        # 根级别的 getters
    ├── mutations.js      # 根级别的 mutation
    ├── mutation-types.js # mutation 事件类型常量    
    └── modules
        ├── index.js    	# 所有模块组装的地方
        └── moduleA.js   	# moduleA模块

  我们在第一步就进行了index的抽离,但是我们发现,如果把所有的状态、方法、异步处理等都写在index.js下,后期处理越来越复杂时,管理起来会非常麻烦,那么我们参照官网,对index再做一层抽离.

link:官网案例

  以上是我的store目录结构,与官网有所差别,但是逻辑是大同小异的,具体抽离根据需求决定。然后import组装就可以了↓

// store/index.js 
import Vue from 'vue'
import Vuex from 'vuex'

import getters from './getters'
import mutations from './mutations'
import actions from './actions'
import modules from './modules/index'

// 1、安装插件
Vue.use(Vuex)

// 2、创建Store对象,这里并不是直接使用new Vuex,二是使用其中的一个Store类
const store = new Vuex.Store({
    state: {        
        counter: 100,
        // ....一般,state不进行抽离,因为Vuex的目的就是管理状态,这样更加直观。
    },
    getters,
    mutations,
    actions,
    modules,
})

// 3、导出对象
export default store

六、总结

  学习Vuex时,掌握好五大基本核心概念十重要,其实每一个概念并不是很难,我们只需要掌握其用法、概念以及他们之间的关系就没什么大问题。最后对于文件的抽离也体现了Vue模块化的思想,如果能理解这一点,对于以后的开发会有很大的帮助,最后祝大家完全掌握这个技能点!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值