1. vuex 理解
1.1. vuex 是什么
- github 站点: https://github.com/vuejs/vuex
- 在线文档: https://vuex.vuejs.org/zh-cn/
- 简单来说: 对vue 应用中多个组件的共享状态进行集中式的管理(读/写)
1.2. 状态自管理应用
- state: 驱动应用的数据源
- view: 以声明方式将state 映射到视图
- actions: 响应在view 上的用户输入导致的状态变化(包含n 个更新状态的方法)
1.3. 多组件共享状态的问题
- 多个视图依赖于同一状态
- 来自不同视图的行为需要变更同一状态
- 以前的解决办法
a. 将数据以及操作数据的行为都定义在父组件
b. 将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递) - vuex 就是用来解决这个问题的
2. vuex 核心概念和API
2.1. state
-
vuex 管理的状态对象
-
它应该是唯一的
const state = {
xxx: initValue
}
2.2. mutations
- 包含多个直接更新state 的方法(回调函数)的对象
- 谁来触发: action 中的commit(‘mutation 名称’)
- 只能包含同步的代码, 不能写异步代码
const mutations = {
yyy (state, {
data1}) {
// 更新state 的某个属性
}
}
2.3. actions
- 包含多个事件回调函数的对象
- 通过执行: commit()来触发mutation 的调用, 间接更新state
- 谁来触发: 组件中: $store.dispatch(‘action 名称’, data1) // ‘zzz’
- 可以包含异步代码(定时器, ajax)
const actions = {
zzz ({
commit, state}, data1) {
commit('yyy', {
data1})
}
}
2.4. getters
- 包含多个计算属性(get)的对象
- 谁来读取: 组件中: $store.getters.xxx
const getters = {
mmm (state) {
return ...
}
}
2.5. modules
- 包含多个module
- 一个module 是一个store 的配置对象
- 与一个组件(包含有共享数据)对应
2.6. 向外暴露store 对象
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
2.7. 组件中
import {
mapState, mapGetters, mapActions} from 'vuex'
export default {
computed: {
...mapState(['xxx']),
...mapGetters(['mmm']),
}
methods: mapActions(['zzz'])
}
{
{
xxx}} {
{
mmm}} @click="zzz(data)"
2.8. 映射store
import store from './store'
new Vue({
store
})
2.9. store 对象
- 所有用vuex 管理的组件中都多了一个属性$store, 它就是一个store 对象
- 属性:
state: 注册的state 对象
getters: 注册的getters 对象 - 方法:
dispatch(actionName, data)
: 分发调用action
3. demo1: 计数器
3.1. store.js
/**
* vuex 的store 对象模块
*/
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
/*
state 对象
类似于data
*/
const state = {
count: 0 // 初始化状态数据
}
/*
mutations 对象
包含个方法: 能直接更新state
一个方法就是一个mutation
mutation 只能包含更新state 的同步代码, 也不会有逻辑
mutation 由action 触发调用: commit('mutationName')
*/
const mutations = {
INCREMENT(state) {
state.count++
},
DECREMENT (state) {
// ctrl + shift + x
state.count--
}
}
/*
actions 对象
包含个方法: 触发mutation 调用, 间接更新state
一个方法就是一个action
action 中可以有逻辑代码和异步代码
action 由组件来触发调用: this.$store.dispatch('actionName')
*/
const actions = {
increment ({
commit}) {
commit('INCREMENT')
},
decrement ({
commit}) {
commit('DECREMENT')
},
incrementIfOdd ({
commit, state}) {
if(state.count%2===1) {
commit('INCREMENT')
}
},
incrementAsync ({
commit}) {
setTimeout(() => {
commit('INCREMENT')
}, 1000)
}
}
/*
getters 对象
包含多个get 计算计算属性方法
*/
const getters = {
oddOrEven (state) {
return state.count%2===0 ? '偶数' : '奇数'
},
count (state) {
return state.count
}
}
// 向外暴露store 实例对象
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
3.2. main.js
import Vue from 'vue'
import app from './app1.vue'
// import app from './app.vue'
import store from './store'
new Vue({
el: '#app',
render: h => h(app),
store // 所有组件都多个一个属性: $store
})
3.3. app.vue(未优化前)
<template>
<div>
<p>clicked: {
{$store.state.count}} times, count is {
{oddOrEven}}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>
</div>
</template>
<script>
export default {
computed: {
oddOrEven () {
return this.$store.getters.oddOrEven
}
},
methods: {
increment () {
this.$store.dispatch('increment')
},
decrement () {
this.$store.dispatch('decrement')
},
incrementIfOdd () {
this.$store.dispatch('incrementIfOdd')
},
incrementAsync () {
this.$store.dispatch('incrementAsync')
}
}
}
</script>
<style>
</style>
3.4. app2.vue(优化后)
<template>
<div>
<p>clicked: {
{count}} times, count is {
{oddOrEven2}}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementIfOdd">increment if odd</button>
<button @click="incrementAsync">increment async</button>
</div>
</template>
<script>
import {
mapGetters, mapActions} from 'vuex'
export default {
computed: mapGetters({
// 名称不一样
oddOrEven2: 'oddOrEven',
count: 'count'
}),
methods: mapActions(['increment', 'decrement', 'incrementIfOdd', 'incrementAsync']) // 名称一样
}
</script>
<style>
</style>
4. demo2: todo list
todo list.
3.1. store/types.js
/**
* 包含多个mutation name
*/
export const RECEIVE_TODOS = 'receive_todos'
export const ADD_TODO = 'add_todo'
export const REMOVE_TODO = 'remove_todo'
export const DELETE_DONE = 'delete_done'
export const UPDATE_ALL_TODOS = 'update_all_todos'
3.2. store/mutations.js
import {
RECEIVE_TODOS, ADD_TODO, REMOVE_TODO, DELETE_DONE, UPDATE_ALL_TODOS} from
'./types'
export default {
[RECEIVE_TODOS] (state, {
todos}) {
state.todos = todos
},
[ADD_TODO] (state, {
todo}) {
state.todos.unshift(todo)
},
[REMOVE_TODO] (state, {
index}) {
state.todos.splice(index, 1)
},
[DELETE_DONE] (state) {
state.todos = state.todos.filter(todo => !todo.complete)
},
[UPDATE_ALL_TODOS] (state, {
isCheck}) {
state.todos.forEach(todo => todo.complete = isCheck)
}
}
3.3. store/actions.js
import storageUtil from '../util/storageUtil'
import {
RECEIVE_TODOS, ADD_TODO, REMOVE_TODO, DELETE_DONE, UPDATE_ALL_TODOS} from './types'
export default {
readTodo ({
commit}) {
setTimeout(() => {
const todos = storageUtil.fetch()
// 提交commit 触发mutation 调用
commit(RECEIVE_TODOS, {
todos})
}, 1000)
},
addTodo ({
commit}, todo) {
commit(ADD_TODO, {
todo})
},
removeTodo ({
commit}, index) {
commit(REMOVE_TODO, {
index})
},
deleteDone ({
commit}) {
commit(DELETE_DONE)
},
updateAllTodos ({
commit}, isCheck) {
commit(UPDATE_ALL_TODOS, {
isCheck})
}
}
3.4. store/getters.js
export default {
todos (state) {
return state.todos
},
totalSize (state) {
return state.todos.length
},
completeSize (state) {
return state.todos.reduce((preTotal, todo) => {
return preTotal + (todo.complete ? 1 : 0)
}, 0)
},
isAllComplete (state, getters) {
return getters.totalSize===getters.completeSize && getters.totalSize>0
}
}
3.5. store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
Vue.use(Vuex)
const state = {
todos: []
}
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
3.6. components/app.vue
<template>
<div class="todo-container">
<div class="todo-wrap">
<todo-header></todo-header>
<todo-main></todo-main>
<todo-footer></todo-footer>
</div>
</div>
</template>
<script>
import todoHeader from './todoHeader.vue'
import todoMain from './todoMain.vue'
import todoFooter from './todoFooter.vue'
import storageUtil from '../util/storageUtil'
export default {
created () {
// 模拟异步读取数据
this.$store.dispatch('readTodo')
},
components: {
todoHeader,
todoMain,
todoFooter
}
}
</script>
<style>
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>
3.7. components/todoHeader.vue
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认"
v-model="title" @keyup.enter="addItem"/>
</div>
</template>
<script type="text/ecmascript-6">
export default {
data () {
return {
title: null
}
},
methods: {
addItem () {
const title = this.title && this.title.trim()
if (title) {
const todo = {
title,
complete: false
}
this.$store.dispatch('addTodo', todo)
this.title = null
}
}
}
}
</script>
<style>
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
</style>
3.8. components/todoMain.vue
<template>
<ul class="todo-main">
<todo-item v-for="(todo, index) in todos" :todo="todo" :key="index" :index="index"></todo-item>
</ul>
</template>
<script type="text/ecmascript-6">
import todoItem from './todoItem'
import storageUtil from '../util/storageUtil'
export default {
components: {
todoItem
},
computed: {
todos () {
return this.$store.getters.todos
}
},
watch: {
todos: {
// 深度监视todos, 一旦有变化立即保存
}
}
}
</script>