④Vuex基本用法


本人是个新手,写下博客用于自我复习、自我总结。
如有错误之处,请各位大佬指出。
学习资料来源于:尚硅谷


vuex 是什么

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

现在先写个vue版的代码,之后来看看vuex会是什么样。

main.js

/* 入口JS */
import Vue from 'vue'
import App from './App.vue'

/* eslint-disable no-new */
new Vue({
  el: '#app',
  components: {App}, // 映射组件标签
  template: '<App/>' // 指定需要渲染到页面的模板
})

App.vue

<template>
  <div>
    <p>click {{count}} times, count is {{evenOrOdd}}</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 {
    data () {
      return{
        count: 0
      }
    },
    computed: {
      evenOrOdd(){
        return this.count%2===0 ? '偶数' : '奇数'
      }
    },
    methods: {
      increment(){
        this.count = this.count + 1
      },
      decrement(){
        this.count = this.count - 1
      },
      incrementIfOdd(){
        if(this.count % 2 === 1){
          this.count = this.count + 1
        }
      },
      incrementAsync(){
        setTimeout(() => {
          this.count = this.count + 1
        },1000)
      }
    }
  }
</script>

<style>

</style>

状态自管理应用

  1. state: 驱动应用的数据源(就相当于上面代码中的data,count)
  2. view: 以声明方式将 state 映射到视图(就相当于上面代码中的template部分)
  3. actions: 响应在 view 上的用户输入导致的状态变化(包含 n 个更新状态的函数)
    在这里插入图片描述
    即:在模板界面上(View),通过事件触发某个Actions函数,再去修改状态数据(State),然后状态数据再去让界面(View)更新显示。

多组件共享状态的问题

  1. 多个视图依赖于同一状态
  2. 来自不同视图的行为需要变更同一状态
  3. 以前的解决办法
    a. 将数据以及操作数据的行为都定义在父组件
    b. 将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递)
    c. 但是这样做只能处理简单的情况,更复杂的情况比较棘手,比如更多路由。
  4. vuex 就是用来解决这个问题的

vuex 核心概念和 API

在这里插入图片描述

state

  1. vuex 管理的状态对象
  2. 它应该是唯一的
const state = {
	xxx: initValue
}

mutations

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

actions

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

getters

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

modules

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

向外暴露 store 对象

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

组件中

import {mapState, mapGetters, mapActions} from 'vuex' 
export default {
	computed: {
		...mapState(['xxx']), 
		...mapGetters(['mmm']), 
	}
	methods: mapActions(['zzz'])
}
{{xxx}} {{mmm}} @click="zzz(data)"

映射 store

import store from './store' 
new Vue({
	store
})

store 对象

  1. 所有用 vuex 管理的组件中都多了一个属性$store, 它就是一个 store 对象
  2. 属性:
    state: 注册的 state 对象
    getters: 注册的 getters 对象
  3. 方法:
    dispatch(actionName, data): 分发调用 action

vuex简例

项目结构:(对最开始的代码进行修改)
在这里插入图片描述

首先安装 npm i --save vuex

store.js

/*
vuex最核心的管理对象store
 */
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

/*
相当于data对象的状态对象
 */
const state = {
  count: 0  // 指定初始化数据
}

/*
包含了n个直接更新状态的方法的对象
 */
const mutations = {
  INCREMENT (state) {
    state.count++
  },
  DECREMENT (state) {
    state.count--
  }
}

/*
包含了n个间接更新状态的方法的对象
 */
const actions = {
  //增加的action
  increment ({commit}) {
    // 提交一个mutation请求
    commit('INCREMENT')
  },
  //减少的action
  decrement ({commit}) {
    // 提交一个mutation请求
    commit('DECREMENT')
  },
  //带条件的action
  incrementIfOdd ({commit, state}) {
    if(state.count%2===1) {
      // 提交一个mutation请求
      commit('INCREMENT')
    }
  },
  //异步的action
  incrementAsync ({commit}) {
    //在action中直接就可以执行异步代码
    setTimeout(() => {
      // 提交一个mutation请求
      commit('INCREMENT')
    }, 1000)
  },
}

/*
包含多个getter计算属性的对象
 */
const getters = {
  evenOrOdd (state) { // 当读取属性值时自动调用并返回属性值
    return state.count%2===0 ? '偶数' : '奇数'
  }
}

/*
向外暴露 store 对象
 */
export default new Vuex.Store({
  state, //状态对象
  mutations, //包含多个更新state函数的对象
  actions, //包含多个对应事件回调函数的对象
  getters //包含多个getter计算属性函数的对象
})

main.js

/*
入口js
 */
import Vue from 'vue'
import Counter from './Counter.vue'
import store from './store'

new Vue({
  el: '#app',
  components: { Counter },
  template: '<Counter/>',
  store // 注册vuex的store: 所有组件对象都多一个属性$store
})

Counter1.vue(原始版本,Counter为对Counter1的改进版,在之后)

<template>
  <div>
    <p>click {{count}} times, count is {{evenOrOdd}}</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 {

    mounted () {
      console.log(this.$store)
    },

    computed: {
      count () {
        return this.$store.state.count
      },
      evenOrOdd () {
        return this.$store.getters.evenOrOdd
      }
    },

    methods: {
      //增加
      increment () {
        //通知vuex去增加
        this.$store.dispatch('increment') //触发store中对应的action调用
      },
      //减少
      decrement () {
        this.$store.dispatch('decrement')
      },
      //如果是奇数才增加
      incrementIfOdd () {
        this.$store.dispatch('incrementIfOdd')
      },
      //过1s才增加
      incrementAsync () {
        this.$store.dispatch('incrementAsync')
      }
    }
  }
</script>

<style>

</style>

它的整体思路就是:
首先建一个store.js,用来存放Vuex相关内容,在其中:
①引入Vuex

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

Vue.use(Vuex)

②向外暴露store对象(基本上每次都会暴露这几个对象)

export default new Vuex.Store({
  state, //状态对象
  mutations, //包含多个更新state函数的对象
  actions, //包含多个对应事件回调函数的对象
  getters //包含多个getter计算属性函数的对象
})

③格式比较固定,暴露之后,肯定要对以上所有对象进行实现:

/*
相当于data对象的状态对象
 */
const state = { //初始化数据
	count: 0 
}

/*
包含了n个直接更新状态的方法的对象
 */
const mutations = {
  INCREMENT (state) {
    state.count++
  }
}

/*
包含了n个间接更新状态的方法的对象
 */
const actions = {
  increment ({commit}) {
    // 提交一个mutation请求
    commit('INCREMENT')
  },
  incrementIfOdd ({commit, state}) {
    if(state.count%2===1) {
      // 提交一个mutation请求
      commit('INCREMENT')
    }
  }
}

/*
包含多个getter计算属性的对象
 */
const getters = {
  evenOrOdd (state) { // 当读取属性值时自动调用并返回属性值
    return state.count%2===0 ? '偶数' : '奇数'
  }
}

那么这里面最基本的用法就是,先在组件中调用actions中的方法,然后在actions中去调用mutations中的方法,用来直接对state数据进行改变(更新状态)。

全部都弄好后,即然要在组件中使用,就要想办法创建个store对象,即在main.js中:
①导入组件和store

import Vue from 'vue'
import Counter from './Counter.vue'
import store from './store'

②注册vuex的store,这样一来所有组件对象都会多一个属性$store,这样之后就能使用store中的方法了。

new Vue({
  el: '#app',
  components: { Counter },
  template: '<Counter/>',
  store // 注册vuex的store: 所有组件对象都多一个属性$store
})

注册好之后,就要考虑如何去使用了,进入组件中。因为上面说到,我们在组件中调用actions中的方法,在actions中设置时会去帮我们调用mutations中的方法,所以在组件中,使用store对象目前只涉及到三种用法:
this.$store.state.count
this.$store.getters.evenOrOdd
this.$store.dispatch('increment')
分别可以调用store对象中的state、getters和actions。注意调用actions用的是dispatch,这个看上面的图就发现了,规定用的就是dispatch。

这样一来就可以连接起每个部分了。

在上面还说到有个简化版,Counter.vue:

<template>
  <div>
    <p>click {{count}} times, count is {{evenOrOdd}}</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 {mapState, mapGetters, mapActions} from 'vuex'

  export default {

    mounted () {
      console.log(this.$store)
    },

    computed: {
      ...mapState(['count']),
      ...mapGetters(['evenOrOdd'])
    },

    methods: {
      ...mapActions(['increment', 'decrement', 'incrementIfOdd', 'incrementAsync'])
    }
  }
</script>

<style>

</style>

因为store里的每个对象可能会有多个方法,我们一个一个去调用还是很麻烦的,现在就可以一次性全部导入。
首先要引入import {mapState, mapGetters, mapActions} from 'vuex',然后使用。分别为:
...mapState([])
...mapGetters([])
...mapActions([])
注意这时候调用actions,可不是dispatch了。

最后再通过结构图理解一下:
在这里插入图片描述
vuex里有四个结构,state、mutations、actions、getters。
更新state的是mutations、用state进行计算的是getters。
操作mutations的是actions。
这四个都是对象。
state里面包含的数据原本是从vue组件中转移过来的。
而组件要想获取到state里的数据,有四种方式:
$store.statemapState()
$store.gettersmapGetters()

在vue中对标签 / 按钮进行操作,分发事件,触发action调用,用到的就是dispatch()mapActions()。action不能直接对数据进行操作,所以去请求mutation,用到的就是commit()。这时候可能需要获取数据后再commit,所以就涉及到后台backend,用到的就是ajax请求,也就是说在vuex里可以直接进行异步操作。而到了mutation就可以直接更新状态。mutation里还有个开发工具devtool,专门用来监视mutation。


在最后再说几个问题:在正常项目中,vuex的四个结构,即store.js中的四个对象包含的数据 / 方法可能会比较多,都放在store.js中,不太好处理(当然都放在一起也行,还是需要看习惯)。所以,我们可以把它们分开来存放。
在这里插入图片描述
index.js:

/*
vuex核心管理模块store对象
 */
import Vue from 'vue'
import Vuex from 'vuex'

import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'

Vue.use(Vuex)

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

state.js

/*
状态对象模块
 */
export default {
  todos: []
}

getters.js

/*
包含n个基于state的getter计算属性方法的对象模块
 */
export default {
  // 总数量
  totalSize (state) {
    return state.todos.length
  }
}

actions.js

/*
包含n个用于间接更新状态的方法的对象模块
 */
import {ADD_TODO, DELETE_TODO} from './mutation-types'

export default {
  addTodo ({commit}, todo) {
    // 提交一个comutation请求
    commit(ADD_TODO, {todo}) // 传递给mutation的是一个包含数据的对象
  },
  deleteTodo ({commit}, index) {
    commit(DELETE_TODO, {index})
  }
}

mutation-types.js

/*
包含n个mutation名称常量
 */
export const ADD_TODO = 'add_todo' // 添加todo
export const DELETE_TODO = 'delete_todo' // 删除todo

mutations.js

/*
包含n个用于直接更新状态的方法的对象模块
 */
import {ADD_TODO, DELETE_TODO} from './mutation-types'

export default {
  [ADD_TODO] (state, {todo}) {  // 方法名不是ADD_TODO, 而是add_todo
    state.todos.unshift(todo)
  },
  [DELETE_TODO] (state, {index}) {
    state.todos.splice(index, 1)
  }
}

还有就是,我们在main.js里,之前我们做的操作,需要映射组件标签+指定渲染到页面的模板:

/*
入口JS
 */
import Vue from 'vue'
import App from './App.vue'
import store from './store'

/* eslint-disable no-new */
new Vue({
  el: '#app',
  components: {App}, // 映射组件标签
  template: '<App/>', // 指定需要渲染到页面的模板
  store
})

有一个简便写法:

/*
入口JS
 */
import Vue from 'vue'
import App from './App.vue'
import store from './store'

/* eslint-disable no-new */
new Vue({
  el: '#app',
  render: h => h(App),  //简化
  store 
})

这个render是一个渲染函数,它完整写是这样的:

render: function(createElement){
	return createElement(App)  //返回的是<App/>
},

就相当于它会返回一个App标签,然后插入到el里就可以了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

只爭朝夕不負韶華

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值