Vuex资料总结

205 阅读2分钟

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战

Vuex

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

State

Vuex使用单一状态树,用一个对象就包含了全部的应用状态层级,它就是一个唯一的数据源,这就意味着每个应用仅仅只能包含一个store实例。我们来看一下是如何定义state的。

const store = new Vuex.Store({
    state : {
        count : 1,
        name : 'andy',
        age : 12
    }
});

上面的代码中,我们就已经定义了一个state了,那么我们在组件中怎么获取到这个state呢?我们可以在组件的计算属性上返回我们需要的状态,比如:

export default {
    data () {
        return {
            name : 'andy'
        }
    },
    computed : {
        count () {
            return this.$store.state.count;
        },
        name () {
            return this.$store.state.name;
        },
        age () {
            return this.$store.state.age;
        }
    }
}

通过上面的方式,我们可以在计算属性上返回我们当前组件需要使用的状态。但是这样的方式,让代码看起来会有点冗余和重复,这时候我们可以使用mapState函数来帮助我们生成计算属性。

const store = new Vuex.Store({
    state : {
        count : 1,
        name : 'andy',
        age : 12
    }
});
import { mapState } from 'vuex';
export default {
    data () {
        return {
            a : 1
        }
    },
    computed : mapState(['count' , 'name' , 'age'])
}

上面的代码中,我们可以看到,如果组件的计算属性的名称与状态名称一样的话,我们可以在调用mapState函数时传入一个数组,数组的元素就是组件的计算属性的名称,而计算属性的值就是这些名称对应的state的值。

如果计算属性的名称与state的名称不一样呢?那么我们可以是这样的:

export default {
    data () {
        return {
            a : 1
        }
    },
    computed : mapState({
        num : state => state.count,
        who : "name"  // 类似于:who : state => state.name
    })
}

上面代码中,调用mapState函数,传入的是一个对象,对象的key就是计算属性的名称,key对应的value可以是一个函数或者字符串,如果是函数的话,那么函数接受一个state作为参数,返回对应的状态值。

如何将state和组件内的局部计算属性混合在一起使用呢?mapState函数返回的是一个对象,我们可以使用对象的扩展运算符,比如:

export default {
    data () {
        return {
            a : 1
        }
    },
    computed : {
        address () {
            return '广州';
        },
        ...mapState({
            num : state => state.count,
            who : 'name'
        })
    }
}

Getter

有时候我们需要从store中的state中派生出一些状态,比如对列表进行过滤等。Vuex允许我们在store中定义"getters",这个我们可以理解为store的计算属性,就像计算属性一样,getter的返回值会根据它的依赖被缓存起来,只有当它的依赖值发生改变才会重新计算。getter接受state作为第一个参数。

export default {
    data () {
        return {
            a : 1
        }
    },
    computed : {
        completedTodos () {
            return this.$store.getters.completedTodos;
        }
    }
}
const store = new Vuex.Store({
    state : {
        todos : [
            {id : 1 , text : 'andy' , completed : true},
            {id : 2 , text : 'alex' , completed : false},
            {id : 3 , text : 'peter' , completed : true},
        ]
    },
    getters : {
        completedTodos (state) {
            return state.todos.filter(todo => {
                return todo.completed;
            })
        }
    }
});

上面代码中,我们可以在组件中通过$store.getters属性来访问我们需要的值。

我们也可以通过方法来访问,我们可以让getter返回一个函数,来实现给getter传入参数。比如:

const store = new Vuex.Store({
    state : {
        todos : [
            {id : 1 , text : 'andy' , completed : true},
            {id : 2 , text : 'alex' , completed : false},
            {id : 3 , text : 'peter' , completed : true},
        ]
    },
    getters : {
        completedTodos (state) {
            return state.todos.filter(todo => {
                return todo.completed;
            })
        },
        getTodoById (state) {
            return (id) => {
                return state.todos.find(todo => todo.id == id);
            }
        }
    }
});
export default {
    data () {
        return {
            a : 1
        }
    },
    computed : {
        completedTodos () {
            return this.$store.getters.completedTodos;
        },
        currentTodoText () {
            return this.$store.getters.getTodoById(2).text;
        }
    }
}

这里我们需要注意的是,getter在通过方法访问时,每次都会进行调用,而不会缓存结果。

除此之外,我们还可以使用mapGetters函数将store中的getter映射到局部计算属性上。

export default {
    data () {
        return {
            a : 1
        }
    },
    computed : {
        ...mapGetters(['completedTodos'])
    }
}

export default {
    data () {
        return {
            a : 1
        }
    },
    computed : mapGetters({
        todos : 'completedTodos'  // 我们也可以使用不同的计算属性名
    })
}

Mutation

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

const store = new Vuex.Store({
    state : {
        todos : [
            {id : 1 , text : 'andy' , completed : true},
            {id : 2 , text : 'alex' , completed : false},
            {id : 3 , text : 'peter' , completed : true},
        ]
    },
    mutations : {
        addTodo (state , payload) {
            state.todos.push(payload);
        }
    }
});
import { mapState } from 'vuex';
export default {
    data () {
        return {
            a : 1
        }
    },
    computed : mapState(['todos']),
    methods : {
        addTodo () {
            this.$store.commit('addTodo' , {
                id : new Date().getTime(),
                text : Math.random().toFixed(2)
            })
        }
    }
}

这里我们需要注意的是,mutation必须是一个同步函数,如果mutation中有异步操作(比如:请求数据),那么当mutation触发的时候,异步数据都还没有返回。在Vuex中,mutation都是同步事务。

Action

action和mutation类似,不同在于,action提交的是mutation,而不是直接更改状态,action可以包含异步操作。

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

const store = new Vuex.Store({
    state : {
        count : 1
    },
    mutations : {
        increment (state) {
            state.count++;
        }
    },
    actions : {
        increment (context) {
            context.commit('increment');
        }
    }
});
import { mapState } from 'vuex';
export default {
    data () {
        return {
            a : 1
        }
    },
    methods : {
        increment () {
            this.$store.dispatch('increment');
        }
    },
    computed : mapState(['count'])
}

上面的代码感觉使用action有点多余,我直接commit一个mutation就可以,但是我们知道mutation必须是同步事务,而action是没有这个限制的,它里面可以包含异步事务。

const store = new Vuex.Store({
    state : {
        count : 1
    },
    mutations : {
        increment (state) {
            state.count++;
        }
    },
    actions : {
        increment (context) {
            context.commit('increment');
        },
        asyncIncrement (context) {
            setTimeout(() => {
                context.commit('increment');
            } , 2000)
        }
    }
});
import { mapState } from 'vuex';

export default {
    data () {
        return {
            a : 1
        }
    },
    methods : {
        increment () {
            this.$store.dispatch('increment');
        },
        asyncIncrement () {
            this.$store.dispatch('asyncIncrement');
        }
    },
    computed : mapState(['count'])
}

我们可以把异步封装成一个promise,那么我们可以在执行结束之后继续执行下面的逻辑:

asyncIncrementBy (context) {
    return new Promise((resolve , reject) => {
        setTimeout(() => {
            context.commit('increment');
            resolve();
        } , 2000)
    })
}

methods : {
    ...mapActions(['increment']),
    asyncIncrementBy () {
        this.$store.dispatch('asyncIncrementBy').then(res => {
            console.log(res)
            console.log(2324)
        })
    }
}

或者我们也可以使用async/await来进行异步流程控制。

Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,这样会显得store对象非常的臃肿。Vuex允许我们将store分割成模块,每个模块都有自己的state,mutation,action,getter。