【Vue】vuex - 状态自管理应用 - state - view - actions

1. vuex 理解

1.1. vuex 是什么

  1. github 站点: https://github.com/vuejs/vuex
  2. 在线文档: https://vuex.vuejs.org/zh-cn/
  3. 简单来说: 对vue 应用中多个组件的共享状态进行集中式的管理(读/)

1.2. 状态自管理应用

  1. state: 驱动应用的数据源
  2. view: 以声明方式将state 映射到视图
  3. actions: 响应在view 上的用户输入导致的状态变化(包含n 个更新状态的方法)
    在这里插入图片描述

1.3. 多组件共享状态的问题

  1. 多个视图依赖于同一状态
  2. 来自不同视图的行为需要变更同一状态
  3. 以前的解决办法
    a. 将数据以及操作数据的行为都定义在父组件
    b. 将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递)
  4. vuex 就是用来解决这个问题的

2. vuex 核心概念和API

2.1. state

  1. vuex 管理的状态对象

  2. 它应该是唯一的

const state = {
    
    
	xxx: initValue
}

2.2. mutations

  1. 包含多个直接更新state 的方法(回调函数)的对象
  2. 谁来触发: action 中的commit(‘mutation 名称’)
  3. 只能包含同步的代码, 不能写异步代码
const mutations = {
    
    
	yyy (state, {
    
    data1}) {
    
    
		// 更新state 的某个属性
	}
}

2.3. actions

  1. 包含多个事件回调函数的对象
  2. 通过执行: commit()来触发mutation 的调用, 间接更新state
  3. 谁来触发: 组件中: $store.dispatch(‘action 名称’, data1) // ‘zzz’
  4. 可以包含异步代码(定时器, ajax)
const actions = {
    
    
	zzz ({
    
    commit, state}, data1) {
    
    
		commit('yyy', {
    
    data1})
	}
}

2.4. getters

  1. 包含多个计算属性(get)的对象
  2. 谁来读取: 组件中: $store.getters.xxx
const getters = {
    
    
	mmm (state) {
    
    
		return ...
	}
}

2.5. modules

  1. 包含多个module
  2. 一个module 是一个store 的配置对象
  3. 与一个组件(包含有共享数据)对应

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 对象

  1. 所有用vuex 管理的组件中都多了一个属性$store, 它就是一个store 对象
  2. 属性:
    state: 注册的state 对象
    getters: 注册的getters 对象
  3. 方法:
    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>

猜你喜欢

转载自blog.csdn.net/weixin_44972008/article/details/113921381