VueX学习笔记

VueX

介绍

VueX是适用于在Vue项目开发时使用的状态管理工具。试想一下,如果在一个项目开发中频繁的使用组件传参的方式来同步data中的值,一旦项目变得很庞大,管理和维护这些值将是相当棘手的工作。为此,Vue为这些被多个组件频繁使用的值提供了一个统一管理的工具——VueX。在具有VueXVue项目中,我们只需要把这些值定义在VueX中,即可在整个Vue项目的组件中使用。

安装

以下步骤的前提是你已经完成了Vue项目构建,并且已转至该项目的文件目录下。

  • npm安装VueX
npm i vuex -s
  • 在项目的根目录下新增一个store文件夹,在改文件夹内创建index.js
    目录结构

使用

1、初始化store下的index中的内容

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

// 挂载Vuex
Vue.use(Vuex);

// 创建VueX对象
const store = new Vuex.Store({
    state:{
        // 存放的键值对就是所要管理的状态
        name:'helloVueX',
        bumber: 0,
        list: [
            {id: 1, name: '1111'},
            {id: 2, name: '2222'}
        ]
    },
    getters: {},
    mutations: {},
    actions: {},
    modules: {}
});

export default store;

2、将store挂载到当前项目的Vue实例当中去(main.js)
挂载3、在组件中使用Vuex

<template>
  <div id="app">

    <img src="./images/logo.png" alt="华宇信息">
    <h1>前后端分离工程结构</h1>

    name:
    <h1>{{$store.state.name}}</h1>

  </div>
</template>

4、什么时候应该用VueX呢?

  • 这个问题因人而异,如果不需要开发大型的单页应用,此时完全没有必要使用vuex,比如页面就两三个,使用vuex后增加的文件比现在的页面还要多,那就咩这个必要了。
  • 假如项目达到了中大型应用的规模,此时会考虑如何更好地在组件外部管理状态,Vuex将会成为自然而然的选择。

核心概念

1、State
  • Vuex就是一个仓库,仓库里面放了很多对象。其中state就是数据源存放地,对应于一般Vue对象里面的data
  • state里面存放的数据是响应式的,Vue组件中从store中读取数据,若是store中的数据发生了改变,依赖这个数据的组件也会发生改变。
1.1在Vue组件中获得Vuex状态

官方建议我们以上操作$store.state.XXX最好放在计算属性中,这样可以让我们的代码看起来更优雅一些,就像这样:

<template>
    <div id="app">
        name:
        <h1>{{getName}}</h1>

    </div>
</template>

<script>
export default {
    name: 'App',
    computed: {
        getName() {
            return this.$store.state.name;
        }
    }
};
</script>

每当store.state.name变化的时候,都会重新求取计算属性,并且更新相关联的DOM。

1.2 mapState辅助函数

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

<template>
    <div id="app">
        <h1>name: {{name}}</h1>

        <h1>nameAlias: {{nameAlias}}</h1>

        <h1>countNumber: {{countNumber}}</h1>

    </div>
</template>

<script>
import {mapState} from 'vuex';
export default {
    name: 'App',
    computed: mapState({
        name: state => state.name,

        // 赋别名,赋别名的话,这里接收对象,而不是数组
        nameAlias: 'name',

        // 为了能够使用 `this` 获取局部状态,必须使用常规函数
        countNumber(state) {
            return state.number + 2;
        }
    })
};
</script>

在这里插入图片描述

1.3 对象展开运算符

mapState函数返回的是一个对象。我们如何将它与局部计算

<script>
import {mapState} from 'vuex';
export default {
    name: 'App',
    computed: {
        ...mapState({
            name: 'name',
            nameAlias: 'name',
            countNumber: function (state) {
                return state.number + 2;
            }
        })
    }
};
</script>

<script>
import {mapState} from 'vuex';
export default {
    name: 'App',
    computed: {
        ...mapState(['name', 'number', 'list'])
    }
};
</script>
2、了解修饰器:Getter

设想一个场景,我们已经将store中的list.name展示在页面上了,而且是很多页面展示了,此时需求说:“所有的list name前面都要加上 hello”;设施后你第一次想到怎么加呢?在每个页面上,使用this.$store.state.list获取到值之后,进行遍历,前面追加"hello"即可。

  • 假如你在A、B、C三个页面都用到了name,那么你要在这A、B、C三个页面都修改一遍,多个页面你就要加很多遍这个方法,造成代码冗余,很不好;
  • 假如下次需求让你把 “hello” 改成 “fuck” 的时候,你又得把三个页面都改一遍,这时候你只能抽自己的脸了…

吸取上面的教训,你会有一个新的思路:我们可以直接在store中对name进行一些操作或者加工,从源头解决问题!那么具体应该怎么写呢?这时候,本次将要介绍的这个Getter利器闪亮登场!

const store = new Vuex.Store({
    state:{
        // 存放的键值对就是所要管理的状态
        name:'helloVueX',
        number: 0,
        list: [
            {id: 1, name: '1111'},
            {id: 2, name: '2222'}
        ]
    },
    getters: {
        getMessage(state) {
            let _obj = JSON.stringify(state.list);
            let arr = JSON.parse(_obj);
            arr.forEach(val => {
                val.name = `hello ${val.name}`;
            });
            return arr;
        }
    },
    mutations: {},
    actions: {},
    modules: {}
});
2.1 通过属性去访问

Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值:

<h1 v-for="item in $store.getters.getMessage" :key="item.id">{{item.name}}</h1>

<template>
    <div id="app">

        <h1 v-for="item in getMessage" :key="item.id">{{item.name}}</h1>

    </div>
</template>

<script>
export default {
    name: 'App',
    computed: {
        getMessage() {
            return this.$store.getters.getMessage;
        }
    }
};
</script>
2.2 通过方法去访问

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

getters: {
    getMessageById: (state) => (id) => {
        return state.list.find(val => val.id === id);
    }
}
<script>
export default {
    name: 'App',
    computed: {
        getMessageById() {
            return this.$store.getters.getMessageById(2);
        }
    }
};
</script>

在这里插入图片描述
!!!注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。

2.3 mapGetters辅助函数

mapGetters 辅助函数仅仅是将 store中的getter 映射到局部计算属性,官方建议我们可以使用mapGetters去解构到计算属性中,就像使用mapState一样,就可以直接使用this调用了,就像下面这样:

<template>
    <div id="app">
        <h1 v-for="item in getMessage" :key="item.id">{{item.name}}</h1>
    </div>
</template>

<script>
import {mapGetters} from 'vuex';
export default {
    name: 'App',
    computed: {
        ...mapGetters(['getMessage'])
    }
};
</script>
3、了解如何修改值:Mutation

说到修改值,有的同学就会想到这样写:

// 错误示范
this.$store.state.XXX = XXX;

为什么上面是错误的写法?因为这个store仓库比较奇怪,你可以随便拿,但是你不能随便改,举个例子:

假如你打开微信朋友圈,看到你的好友发了动态,但是动态里有个错别字,你要怎么办呢?你可以帮他改掉吗?当然不可以!我们只能通知他本人去修改,因为是别人的朋友圈,你是无权操作的,只有他自己才能操作,同理,在vuex中,我们不能直接修改仓库里的值,必须用vuex自带的方法去修改,这个时候,Mutation闪亮登场了!

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数,你可以想store.commit传入额外的参数,即mutation载荷

mutations: {
        incrementN(state, payload) {
            state.number += payload;
        },

         // 对象风格的提交方式
        increment(state, payload) {
            state.number += payload.amount;
        }
}
<script>
export default {
    name: 'App',
    mounted() {
        console.log(`旧值:${this.$store.state.number}`);
        this.$store.commit('incrementN', 5);
        console.log(`新值N:${this.$store.state.number}`);
        // 对象风格的提交方式
        this.$store.commit('increment', {amount: 10});
        console.log(`新值:${this.$store.state.number}`);
    }
};
</script>

在这里插入图片描述

3.1 Mutation 需遵守 Vue 的响应规则

既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。
这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:

  • 最好提前在你的 store 中初始化好所有所需属性。
  • 当需要在对象上添加新属性时,你应该:使用 Vue.set(obj, 'newProp', 123)
3.2 Mutation 必须是同步函数

这里说一条重要原则:Mutations里面的函数必须是同步操作,不能包含异步操作!(别急,后面会讲到异步)。

3.3 在组件中提交 Mutation

就像最开始的mapStatemapGetters一样,我们在组件中可以使用mapMutations以代替this.$store.commit('XXX'),是不是很方便呢?

你可以在组件中使用this.$store.commit('xxx') 提交mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit调用(需要在根节点注入store)。

<script>
import {mapState, mapMutations} from 'vuex';
export default {
    name: 'App',
    computed: {
        ...mapState(['number'])
    },
    mounted() {
        console.log(`旧值:${this.number}`);
        this.incrementN(5);
        console.log(`新值N:${this.number}`);
        this.increment({amount: 10});
        console.log(`新值:${this.number}`);
    },
    methods: {
        ...mapMutations(['incrementN', 'increment'])
    }
};
</script>
4、了解异步操作:Actions

Actions存在的意义是假设你在修改state的时候有异步操作,vuex作者不希望你将异步操作放在Mutations中,所以就给你设置了一个区域,让你放异步操作,这就是Actions。

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

注册一个简单的action:

const store = new Vuex.Store({
    state:{
        // 存放的键值对就是所要管理的状态
        name:'helloVueX',
        number: 0
    },
    mutations: {
        setNumberIsWhat(state, payload) {
            state.number = payload.number;
        }
    },
    actions: {
        incrementAction(context) {
            // 我们模拟一个异步操作,1秒后修改number为888
            return new Promise(resolve => {
                setTimeout(() => {
                    context.commit('setNumberIsWhat', {number: 888});
                    resolve();
                }, 1000);
            });
        }
    }
});

组件内 vue.js

mounted() {
        console.log(`旧值:${this.number}`);
        this.$store.dispatch('incrementAction').then(() => {
            console.log(`新值:${this.number}`);
        });
    },

在这里插入图片描述
Action 函数接受一个与 store 实例具有相同方法和属性的context对象,因此你可以调用context.commit提交一个 mutation,或者通过 context.statecontext.getters 来获取 stategetters

4.1 分发Action

Action通过 store.dispatch方法触发:

store.dispatch('incrementAction')

Actions 支持同样的载荷方式和对象方式进行分发:

// 以载荷形式分发
store.dispatch('incrementAction', {
  amount: 10
})

// 以对象形式分发
store.dispatch({
  type: 'incrementAction',
  amount: 10
})
4.2 在组件内分发Action

你如果不想一直使用this.$store.dispatch('XXX')这样的写法调用action,你可以采用mapActions的方式,把相关的actions解构到methods中,用this直接调用。

import {mapActions, mapState} from 'vuex';
export default {
    name: 'App',
    computed: {
        ...mapState(['number'])
    },
    mounted() {
        console.log(`旧值:${this.number}`);
        this.incrementAction().then(() => {
            console.log(`新值:${this.number}`);
        });
    },
    methods: {
        ...mapActions(['incrementAction'])
    }
};
5、了解模块化状态管理:Modules

当项目庞大,状态非常多时,可以采用模块化管理模式。Vuex 允许我们将 store分割成模块(module)。每个模块拥有自己的statemutationactiongetter、甚至是嵌套子模块——从上至下进行同样方式的分割。

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 的状态
5.1 模块的局部状态
  • 对于模块内部的 mutationgetter,接收的第一个参数是模块的局部状态对象
  • 对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:
  • 对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
const moduleA = {
    // namespaced: true,
    state: () => ({
        name:'helloModuleA',
        number: 2
    }),
    mutations: {
        increment(state, payload) {
            // state.number = 2
            state.number += payload;
        }
    },
    getters: {
        getMessage(state, getters, rootState) {
            return state.number + rootState.number; //  2 + 0 = 2
        }
    },
    actions: {
        getSumAction(context) {
            if (context.state.number === 2 && context.rootState.number === 0) {
                context.commit('increment');
            }
        }
    }
};
const store = new Vuex.Store({
    state:{
        // 存放的键值对就是所要管理的状态
        name:'helloVueX',
        number: 0
    },
    getters: {
        getMessage(state) {
            return `hello ${state.name}`;
        }
    },
    mutations: {
        increment(state, payload) {
            // state.number = 0
            state.number += payload; 
        }
    },
    modules: {
        a: moduleA
    }
});
5.2 命名空间

默认情况下,模块内部的 actionmutationgetter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutationaction 作出响应。

如果希望你的模块具有更高的封装度复用性,你可以通过添加namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getteractionmutation 都会自动根据模块注册的路径调整命名。

mounted() {
        console.log(`旧值:${this.number}`); // 0
        console.log(`a模板旧值:${this.$store.state.a.number}`); // 2
        this.$store.commit('increment', 5);
        this.$store.commit('a/increment', 40);
        console.log(`新值:${this.number}`); // 5
        console.log(`a模板新值:${this.$store.state.a.number}`); // 42
}

辅助函数用法:

<script>
import {mapState, mapGetters, mapMutations, mapActions} from 'vuex';
export default {
    name: 'App',
    computed: {
        // ...mapState(['number']),

        ...mapState({
            number: state => state.number,
            numberA: state => state.a.number
        }),

        // ...mapGetters(['getMessage'])

        ...mapGetters(['getMessage', 'a/getSum'])
    },
    mounted() {
        // console.log(this.number);
        // console.log(this.numberA);

        // console.log(`a模板旧值:${this.numberA}`);
        // this['a/increment'](40);
        // console.log(`a模板新值:${this.numberA}`);


        // console.log(`${this['a/getSum']}`);

        // this.$store.dispatch('a/getSumAction');
        // this['a/getSumAction']();
        console.log(`a模板新值:${this.numberA}`);
    },
    methods: {
        // ...mapMutations(['increment', 'reduce']),
        ...mapMutations(['a/increment', 'increment', 'reduce']),

        // ...mapActions(['incrementAction'])
        ...mapActions(['a/getSumAction', 'incrementAction'])
    }
};
</script>
5.3 模块动态注册

在 store 创建之后,你可以使用 store.registerModule方法注册模块:

import Vuex from 'vuex'

const store = new Vuex.Store({ /* 选项 */ })

// 注册模块 `ModuleA`
store.registerModule('ModuleA', {
  // ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['Modulea', 'ModuleA'], {
  // ...
})

之后就可以通过 store.state.ModuleAstore.state.Modulea.ModuleA访问模块的状态。

你可以通过store.hasModule(ModuleA)方法检查该模块是否已经被注册到store

总结

看到这里你肯定对vuex不陌生了,你会安装它,配置它,读取state的值,甚至修饰读(Getter),然后你会修改里面的值了(Mutation),假如你有异步操作并且需要修改state,那你就要使用Action,这样,你就可以在你的项目中用起来vuex啦!加油吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值