【vuex】上手vuex

安装vue-loader后,有两个必须:

  • 必须安装vue-template-compiler
  • 必须在webpack配置文件的plugins字段配置VueLoaderPlugin
const {VueLoaderPlugin} = require("vue-loader");
module.exports = {
	plugins:[
	    new VueLoaderPlugin()
	]
}

不使用vuex

<template>
    <div>
        <h4 v-bind:class='["title"]'>{{title}}</h4>
        <span v-bind:class='["count"]'>{{count}}</span>
        <button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
        <button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
    </div>
</template>

<script>
import {mapState} from "vuex";
export default {
    props:{
        "title":{
            type:String,
            default:"计数器"
        },
        "value":{
            type:Number,
            default:0
        }
    },
    data:function(){
        return {
            count:this.value
        }
    },
    methods:{
        handleIncrement:function(){
            this.count++;
        },
        handleDecrement:function(){
            this.count--;
        }
    }
}
</script>

不传参,使用props默认值

const vm = new Vue({
    render:function(createElement){
        return createElement(Counter);
    }
}).$mount("#root");

自己传参

const vm = new Vue({
    render:function(createElement){
        return createElement(
            Counter,
            {
                attrs:{
                    title:"one",
                    value:10
                }
            }
        )
    }
}).$mount("#root");

使用vuex

每个应用仅包含一个store实例,从应用根组件注入store实例,整个应用中的组件都可以访问store中的状态。

import store from "./store.js";

const vm = new Vue({
    el:"#root",
    store:store,
    render:h => h(Counter)
})

Vue组件从store中获取状态,一旦store中的状态发生变化,相应的Vue组件会同步更新。

不能直接修改store中的状态,只能通过commit mutations来实现。

  • store
    • state
    • getters
    • mutations
    • actions
    • commit()
    • dispatch()

store中的state

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

const store = new Vuex.Store({
    state:{
        count:0,
        title:"计数器",
    },
    mutations:{
        increment:function(state){
            state.count++;
        },
        decrement:function(state){
            state.count--;
        }
    }
});

export default store;

访问store中的state

第一种方式:this.$store.state

data选项

data选项从store中获取状态,点击"+“或”-",this.$store.state.count值变化,但没有驱动页面渲染

<template>
    <div>
        <h4 v-bind:class='["title"]'>{{title}}</h4>
        <span v-bind:class='["count"]'>{{count}}</span>
        <button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
        <button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
    </div>
</template>

<script>
export default {
    data:function(){
        return {
            title:this.$store.state.title,
            count:this.$store.state.count
        }
    },
    methods:{
        handleIncrement:function(){
            this.$store.commit("increment");
            console.log(this.$store.state.count);
        },
        handleDecrement:function(){
            this.$store.commit("decrement");
            console.log(this.$store.state.count);
        }
    }
}
</script>
计算属性

使用计算属性从store中获取状态,点击"+“或”-",this.$store.state.count值变化,且可以驱动页面渲染

<template>
    <div>
        <h4 v-bind:class='["title"]'>{{title}}</h4>
        <span v-bind:class='["count"]'>{{count}}</span>
        <button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
        <button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
    </div>
</template>

<script>
export default {
    computed:{
        title:function(){
            return this.$store.state.title
        },
        count:function(){
            return this.$store.state.count
        }
    },
    methods:{
        handleIncrement:function(){
            this.$store.commit("increment");
            console.log(this.$store.state.count);
        },
        handleDecrement:function(){
            this.$store.commit("decrement");
            console.log(this.$store.state.count);
        }
    }
}
</script>

第二种方式:辅助函数mapState

<template>
    <div>
        <h4 v-bind:class='["title"]'>{{title}}</h4>
        <span v-bind:class='["count"]'>{{count}}</span>
        <button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
        <button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
    </div>
</template>

<script>
import {mapState} from "vuex";
export default {
    computed:mapState({
        count:state => state.count,
        title:state => state.title
    }),
    methods:{
        handleIncrement:function(){
            this.$store.commit("increment");
        },
        handleDecrement:function(){
            this.$store.commit("decrement");
        }
    }
}
</script>

当映射的计算属性与state的子节点名称相同时,可以给mapState传递一个字符串数组,就像下面这样:

<template>
    <div>
        <h4 v-bind:class='["title"]'>{{title}}</h4>
        <span v-bind:class='["count"]'>{{count}}</span>
        <button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
        <button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
    </div>
</template>

<script>
import {mapState} from "vuex";
export default {
    computed:mapState(["count","title"]),
    methods:{
        handleIncrement:function(){
            this.$store.commit("increment");
        },
        handleDecrement:function(){
            this.$store.commit("decrement");
        }
    }
}
</script>

使用对象扩展运算符...

computed:{
    ...mapState({
        title:state => state.title,
        count:state => state.count
    })
}

或者

computed:{
    ...mapState(["title","count"])
}

store中的getters

getters接受state作为第一个参数,其返回值是根据store中的state推导计算而来,且只有它的依赖项发生变化才会被重新计算,类似于计算属性。

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = {
    list:[
        {content:"吃饭",done:false},
        {content:"睡觉",done:true},
        {content:"打豆豆",done:false}
    ]    
}
const getters = {
    list_done:function(state){
        return state.list.filter(item => item.done);
    },
    list_undo:function(state){
        return state.list.filter(item => !item.done);
    }
}

export default new Vuex.Store({
    state,
    getters
});

访问store中的getters

使用this.$store.getters

<template>
    <ul>
        <li v-for="(item,idx) in list" 
            v-bind:key="idx">
            {{item.content}}
        </li>
    </ul>
</template>

<script>

export default {
    computed:{
        list:function(){
            return this.$store.getters.list_done
        }
    }
}
</script>

使用辅助函数mapGetters

<template>
    <ul>
        <li v-for="(item,idx) in list" 
            v-bind:key="idx">
            {{item.content}}
        </li>
    </ul>
</template>

<script>
import {mapGetters} from "vuex";
export default {
    computed:{
        ...mapGetters({
            list:"list_undo"
        })
    }
}
</script>

当映射的计算属性与getters中的节点名称相同时,可以向mapGetters传递一个字符串数组。

<template>
    <ul>
        <li v-for="(item,idx) in list_undo" 
            v-bind:key="idx">
            {{item.content}}
        </li>
    </ul>
</template>

<script>
import {mapGetters} from "vuex";
export default {
    computed:{
        ...mapGetters(["list_undo"])
    }
}
</script>

store中的mutations

mutations非常类似于事件,

mutations:{
    type:fn
}
  • type
    事件类型,是一个字符串
  • fn
    事件处理程序,接受state作为第一个参数,也可以接受第二个参数。
    • 第二个参数是数值、字符串等简单类型
    import Vue from "vue";
    import Vuex from "vuex";
    Vue.use(Vuex);
    
    const state = {
        count:0,
        title:"计数器"   
    }
    const mutations = {
        increment:function(state,n){
            state.count = state.count+n;
        },
        decrement:function(state){
            state.count--;
        }  
    }
    
    export default new Vuex.Store({
        state,
        mutations
    });
    
    <template>
        <div>
            <h4 v-bind:class='["title"]'>{{title}}</h4>
            <span v-bind:class='["count"]'>{{count}}</span>
            <button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
            <button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
        </div>
    </template>
    
    <script>
    import {mapState} from "vuex";
    export default {
        computed:{
            ...mapState(["title","count"])
        },
        methods:{
            handleIncrement:function(){
                this.$store.commit("increment",10);
            },
            handleDecrement:function(){
                this.$store.commit("decrement");
            }
        }
    }
    </script>
    
    • 第二个参数是一个对象
    import Vue from "vue";
    import Vuex from "vuex";
    Vue.use(Vuex);
    
    const state = {
        count:0,
        title:"计数器"
    }
    const mutations = {
        increment:function(state,obj){
            state.count = state.count+obj.step;
        },
        decrement:function(state){
            state.count--;
        }  
    }
    
    export default new Vuex.Store({
        state,
        mutations,
    });
    
    <template>
        <div>
            <h4 v-bind:class='["title"]'>{{title}}</h4>
            <span v-bind:class='["count"]'>{{count}}</span>
            <button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
            <button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
        </div>
    </template>
    
    <script>
    import {mapState} from "vuex";
    export default {
        computed:{
            ...mapState(["title","count"])
        },
        methods:{
            handleIncrement:function(){
                this.$store.commit("increment",{
                    step:20
                });
            },
            handleDecrement:function(){
                this.$store.commit("decrement");
            }
        }
    }
    </script>
    
    当第二个参数是一个对象时,还可以像下面这样commit mutations:
    <template>
        <div>
            <h4 v-bind:class='["title"]'>{{title}}</h4>
            <span v-bind:class='["count"]'>{{count}}</span>
            <button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
            <button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
        </div>
    </template>
    
    <script>
    import {mapState} from "vuex";
    export default {
        computed:{
            ...mapState(["title","count"])
        },
        methods:{
            handleIncrement:function(){
                this.$store.commit({
                    type:"increment",
                    step:10
                })
            },
            handleDecrement:function(){
                this.$store.commit("decrement");
            }
        }
    }
    </script>
    

辅助函数mapMutations

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

const state = {
    count:0,
    title:"计数器" 
}
const mutations = {
    increment:function(state,n){
        if(typeof n==="number"){
            state.count+=n;
        }else{
            state.count++;
        }
    },
    decrement:function(state){
        state.count--;
    }  
}

export default new Vuex.Store({
    state,
    mutations
});
<template>
    <div>
        <h4 v-bind:class='["title"]'>{{title}}</h4>
        <span v-bind:class='["count"]'>{{count}}</span>
        <button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
        <button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
    </div>
</template>

<script>
import {mapState,mapMutations} from "vuex";
export default {
    computed:{
        ...mapState(["title","count"])
    },
    methods:{
        ...mapMutations({
            handleIncrement:"increment",
            handleDecrement:"decrement"
        }),
    },
    mounted:function(){
        setTimeout(() => {
            this.handleIncrement(10);
        },2000)
    }
}
</script>

此时this.handleIncrement将映射为this.$store.commit(increment);
this.handleIncrement(10)将映射为this.$store.commit(increment,10);
mutation必须是同步函数,那如果想要异步操作呢?

store中的actions

store中的actions和mutations类似,不同的是,actions里可以包含任意异步操作。

每个action接受context对象作为第一个参数,context对象具有与store一样的属性和方法,也可以接受第二个参数。

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

const state = {
    count:0,
    title:"计数器"   
}
const mutations = {
    increment:function(state){
        state.count++;
    },
    decrement:function(state){
        state.count--;
    }  
}
const actions = {
    asyncIncrement:function(context){
        setTimeout(() => {
            context.commit("increment");
        },2000);
    },
    asyncDecrement:function(context){
        setTimeout(() => {
            context.commit("decrement");
        },1000);
    }
}

export default new Vuex.Store({
    state,
    mutations,
    actions
});
<template>
    <div>
        <h4 v-bind:class='["title"]'>{{title}}</h4>
        <span v-bind:class='["count"]'>{{count}}</span>
        <button v-bind:class='["btn"]' v-on:click="onIncrement">++</button>
        <button v-bind:class='["btn"]' v-on:click="onDecrement">--</button>
    </div>
</template>

<script>
import {mapState} from "vuex";
export default {
    computed:{
        ...mapState(["title","count"])
    },
    methods:{
        onIncrement:function(){
            this.$store.dispatch("asyncIncrement");
        },
        onDecrement:function(){
            this.$store.dispatch("asyncDecrement");
        }
    }
}
</script>

store.dispatch分发action,等异步操作完成,通过commit mutation来更新store中的state。

action的第二个参数可以是一个数值、字符串等基本类型,也可以是一个对象。

  • 第二个参数是一个数值
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

const state = {
    count:0,
    title:"计数器"   
}
const mutations = {
    increment:function(state,n){
        state.count+=n;
    },
    decrement:function(state){
        state.count--;
    }  
}
const actions = {
    asyncIncrement:function(context){
        setTimeout(() => {
            context.commit("increment",10);
        },2000);
    },
    asyncDecrement:function(context){
        setTimeout(() => {
            context.commit("decrement");
        },1000);
    }
}

export default new Vuex.Store({
    state,
    mutations,
    actions
});
<template>
    <div>
        <h4 v-bind:class='["title"]'>{{title}}</h4>
        <span v-bind:class='["count"]'>{{count}}</span>
        <button v-bind:class='["btn"]' v-on:click="onIncrement">++</button>
        <button v-bind:class='["btn"]' v-on:click="onDecrement">--</button>
    </div>
</template>

<script>
import {mapState} from "vuex";
export default {
    computed:{
        ...mapState(["title","count"])
    },
    methods:{
        onIncrement:function(){
            this.$store.dispatch("asyncIncrement",10);
        },
        onDecrement:function(){
            this.$store.dispatch("asyncDecrement");
        }
    }
}
</script>
  • 第二个参数是一个对象
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

const state = {
    count:0,
    title:"计数器"   
}
const mutations = {
    increment:function(state,obj){
        state.count+=obj.step;
    },
    decrement:function(state){
        state.count--;
    }  
}
const actions = {
    asyncIncrement:function(context,obj){
        setTimeout(() => {
            context.commit("increment",obj);
        },2000);
    },
    asyncDecrement:function(context){
        setTimeout(() => {
            context.commit("decrement");
        },1000);
    }
}

export default new Vuex.Store({
    state,
    mutations,
    actions
});
<template>
    <div>
        <h4 v-bind:class='["title"]'>{{title}}</h4>
        <span v-bind:class='["count"]'>{{count}}</span>
        <button v-bind:class='["btn"]' v-on:click="onIncrement">++</button>
        <button v-bind:class='["btn"]' v-on:click="onDecrement">--</button>
    </div>
</template>

<script>
import {mapState} from "vuex";
export default {
    computed:{
        ...mapState(["title","count"])
    },
    methods:{
        onIncrement:function(){
            this.$store.dispatch("asyncIncrement",{
                step:10
            });
        },
        onDecrement:function(){
            this.$store.dispatch("asyncDecrement");
        }
    }
}
</script>

如果第二个参数是一个对象,也可以按下面这样dispatch action:

<template>
    <div>
        <h4 v-bind:class='["title"]'>{{title}}</h4>
        <span v-bind:class='["count"]'>{{count}}</span>
        <button v-bind:class='["btn"]' v-on:click="onIncrement">++</button>
        <button v-bind:class='["btn"]' v-on:click="onDecrement">--</button>
    </div>
</template>

<script>
import {mapState} from "vuex";
export default {
    computed:{
        ...mapState(["title","count"])
    },
    methods:{
        onIncrement:function(){
            this.$store.dispatch({
                type:"asyncIncrement",
                step:10
            })
        },
        onDecrement:function(){
            this.$store.dispatch("asyncDecrement");
        }
    }
}
</script>

使用辅助函数mapActions

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

const state = {
    count:0,
    title:"计数器"   
}
const mutations = {
    increment:function(state){
        state.count++
    },
    decrement:function(state){
        state.count--;
    }  
}
const actions = {
    asyncIncrement:function(context){
        setTimeout(() => {
            context.commit("increment");
        },2000);
    },
    asyncDecrement:function(context){
        setTimeout(() => {
            context.commit("decrement");
        },1000);
    }
}

export default new Vuex.Store({
    state,
    mutations,
    actions
});
<template>
    <div>
        <h4 v-bind:class='["title"]'>{{title}}</h4>
        <span v-bind:class='["count"]'>{{count}}</span>
        <button v-bind:class='["btn"]' v-on:click="onIncrement">++</button>
        <button v-bind:class='["btn"]' v-on:click="onDecrement">--</button>
    </div>
</template>

<script>
import {mapState,mapActions} from "vuex";
export default {
    computed:{
        ...mapState(["title","count"])
    },
    methods:{
        ...mapActions({
            onIncrement:"asyncIncrement",
            onDecrement:"asyncDecrement"
        })
    }
}
</script>

异步在action中的使用

<template>
    <div>
        <h4 v-bind:class='["title"]'>{{title}}</h4>
        <span v-bind:class='["count"]'>{{count}}</span>
        <button v-bind:class='["btn"]' v-on:click="onIncrement">++</button>
        <button v-bind:class='["btn"]' v-on:click="onDecrement">--</button>
    </div>
</template>

<script>
import {mapState} from "vuex";
export default {
    computed:{
        ...mapState(["title","count"])
    },
    methods:{
        onIncrement:function(){
            this.$store.dispatch("asyncIncrement").then(function(){
                console.log("add success");
            })
        },
        onDecrement:function(){
            this.$store.dispatch("asyncDecrement");
        }
    }
}
</script>
  • new Promise()
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

const state = {
    count:0,
    title:"计数器"   
}
const mutations = {
    increment:function(state){
        state.count++
    },
    decrement:function(state){
        state.count--;
    }  
}
const actions = {
    asyncIncrement:function(context){
        return new Promise(resolve => {
            setTimeout(() => {
                context.commit("increment");
                resolve();
            },2000);
        })
    },
    asyncDecrement:function({commit}){
        const timeout = ms => new Promise((resolve) => setTimeout(resolve,ms));
        timeout(1000).then(function(){
            commit("decrement");
        })
        console.log("sub success")
    }
}

export default new Vuex.Store({
    state,
    mutations,
    actions
});
  • async/await
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

const state = {
    count:0,
    title:"计数器"   
}
const mutations = {
    increment:function(state){
        state.count++
    },
    decrement:function(state){
        state.count--;
    }  
}
const actions = {
    asyncIncrement:function(context){
        return new Promise(resolve => {
            setTimeout(() => {
                context.commit("increment");
                resolve();
            },2000);
        })
    },
    asyncDecrement:async function({commit}){
        await setTimeout(function(){
            commit("decrement");
        },1000);
        console.log("sub success")
    }
}

export default new Vuex.Store({
    state,
    mutations,
    actions
});

@babel/preset-env可以转译ES2017、ES2016、ES2015的最新语法,但不能转译全局对象及API,所以需要安装@babel/plugin-transform-plugin来转译async/await

但是,随后仍遇到问题:

Uncaught TypeError: Cannot assign to read only property ‘exports’ of object

这是ES Module 和 CommonJS混用导致的。

解决方法是,

//webpack.config.js
use:{
    loader:"babel-loader",
    options:{
        presets:["@babel/preset-env"],
        plugins:["@babel/plugin-transform-runtime"],
        sourceType: 'unambiguous' 
    }
}

sourceType: 'unambiguous'让babel严格区分 ES Module文件和CommonJS文件。

store中的module

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

const counter = {
    state:() => ({
        count:0,
        title:"计数器"
    }),
    getters:{
        another_count:function(state){
            return state.count+1;
        }
    },
    mutations:{
        increment:function(state){
            state.count++
        },
        decrement:function(state){
            state.count--;
        }          
    },
    actions:{
        "asyncIncrement":function(context){
            setTimeout(() => {
                context.commit("increment");
            },1000);
        }
    }
}

const todolist = {
    state:() => ({
        list:[
            {content:"吃饭",done:false},
            {content:"睡觉",done:true},
            {content:"打豆豆",done:false}
        ]
    }),
    getters:{
        list_undo:function(state){
            return state.list.filter(item => !item.done);
        },
        list_done:function(state){
            return state.list.filter(item => item.done);
        }
    },
    mutations:{
        click:function(state){
            if(state.list.length>0){
                state.list.splice(0,1);
            }
        }
    },
    actions:{
        asyncClick:function(context){
            setTimeout(()=>{
                context.commit("click");
            },1000);
        }
    }
}

export default new Vuex.Store({
    modules:{
        counter,
        todolist
    }
})
<template>
    <div>
        <h4 v-bind:class='["title"]'>{{title}}</h4>
        <span v-bind:class='["count"]'>{{count}}</span>
        <button v-bind:class='["btn"]' v-on:click="handleIncrement">+</button>
        <button v-bind:class='["btn"]' v-on:click="handleDecrement">-</button>
        <button v-bind:class='["btn"]' v-on:click="handleAsyncIncrement">异步-</button>
    </div>
</template>

<script>
export default {
    computed:{
        title:function(){
            console.log(Object.keys(this.$store.state));
            return this.$store.state.counter.title
        },
        count:function(){
            return this.$store.state.counter.count
        }
    },
    methods:{
        handleIncrement:function(){
            this.$store.commit("increment");
        },
        handleDecrement:function(){
            this.$store.commit("decrement");
        },
        handleAsyncIncrement:function(){
            this.$store.dispatch("asyncIncrement")
        }
    }
}
</script>
<template>
<div>
    <ul>
        <li v-for="(item,idx) in list_undo" 
            v-bind:key="idx">
            {{item.content}}
        </li>
    </ul>
    <button v-on:click="handleClick">click me</button>
    <button v-on:click="handleAsyncClick">async click me</button>
</div>
</template>

<script>
import {mapGetters} from "vuex";
export default {
    computed:{
        list_undo:function(){
            console.log(Object.keys(this.$store.getters));
            return this.$store.getters.list_undo
        }
    },
    methods:{
        handleClick:function(){
            this.$store.commit("click");
        },
        handleAsyncClick:function(){
            this.$store.dispatch("asyncClick");
        }
    }
}
</script>

Object.keys(this.$store.state)输出["counter", "todolist"];

Object.keys(this.$store.getters)输出 ["another_count", "list_undo", "list_done"]

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值