3

vuex学习笔记

 3 years ago
source link: http://www.lzhpo.com/article/88
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

vuex学习笔记

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

什么情况下我应该使用 Vuex?

Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。

如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。

vuex计数器.png

App.vue:

<template>  <div id="app">    <p>click {{count}}, 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: {        //增加1        increment () {            const count = this.count;            this.count = count + 1;        },        //减少1        decrement () {            const count = this.count;            this.count = count - 1;        },        //如果是奇数,就增加1        incrementIfOdd () {            const count = this.count;            if (count % 2 === 1) {                this.count = count + 1;            }        },        //过1s才增加1        incrementAsync () {            setTimeout(() => {                const count = this.count;                this.count = count +1;            }, 1000)        },    }}</script><style></style>

main.js:

// The Vue build version to load with the `import` command// (runtime-only or standalone) has been set in webpack.base.conf with an alias.import Vue from 'vue'import App from './App'Vue.config.productionTip = false/* eslint-disable no-new */new Vue({  el: '#app',  components: { App },  template: '<App/>'})

vuex版

  • state:驱动应用的数据源。
  • view:以声明方式将state映射到视图。
  • actions:响应在view上的用户输入导致的状态变化。

单向数据流.png

但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。

App.vue:

<template>  <div id="app">    <p>click {{count}}, 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>  //使用vuex的api  import {mapState, mapGetters, mapActions} from 'vuex';    export default {        computed: {            ...mapGetters(['evenOrOdd']), //mapGetters返回值:evenOrOdd () { return this.$store.getters.evenOrOdd }            ...mapState(['count']), //mapState返回值:count () { return this.$store.state.count }        },        methods: {            ...mapActions(['increment', 'decrement', 'incrementIfOdd', 'incrementAsync'])        }    }</script><style></style>

main.js:

// The Vue build version to load with the `import` command// (runtime-only or standalone) has been set in webpack.base.conf with an alias.import Vue from 'vue'import App from './App'import store from "./store";Vue.config.productionTip = false;/* eslint-disable no-new */new Vue({  el: '#app',  components: { App },  template: '<App/>',  store //所有组件都多了一个对象$store});

store.js:

/*vuex的核心管理模块 */import Vue from "vue";import Vuex from "vuex";Vue.use(Vuex);//状态对象const state = { //初始化状态  count: 0};//包含多个更新state函数的对象const mutations = {  //增加的mutations  INCREMENT (state) {    state.count++  },  //减少的mutations  DECREMENT (state) {    state.count--  }};//包含多个对应事件回调函数的对象const actions = {  //增加的action  increment ({commit}) {    //提交增加的mutation    commit('INCREMENT')  },  //减少的action  decrement ({commit}) {    //提交减少的mutation    commit('DECREMENT')  },  //如果是奇数,就增加1  incrementIfOdd ({commit, state}) {    if (state.count % 2 === 1) {      //提交增加的mutation      commit('INCREMENT')    }  },  //过1s才增加1  incrementAsync ({commit}) {    setTimeout(() => {      //提交增加的mutation      commit('INCREMENT')    }, 1000)  }};//包含多个getter计算属性函数的对象const getters = {  evenOrOdd (state) { //不需要调用,只需要读取属性值    return state.count % 2 === 0 ? '偶数' : '奇数'  }};export default new Vuex.Store({  state, //状态对象  mutations, //包含多个更新state函数的对象  actions, //包含多个对应事件回调函数的对象  getters, //包含多个getter计算属性函数的对象})

vuex结构图

state

vuex 管理的状态对象。

//它应该是唯一的 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) // 'zzz'

  4. 可以包含异步代码(定时器, ajax)

    const actions = {     zzz ({commit, state}, data1) {         commit('yyy', {data1})     } }

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的结构图.png

官方的vuex架构图:

vuex.png

vuex版todolist

todolist操作.png

vuex版todolist的工程图片.png

TodoFooter.vue

<template>  <div class="todo-footer">    <label>      <input type="checkbox" v-model="checkAll"/>    </label>    <span>          <span>已完成{{completeSize}}</span> / 全部{{totalSize}}        </span>    <button class="btn btn-danger" v-show="completeSize" @click="deleteAllCompleted">清除已完成任务</button>  </div></template><script>  import {mapGetters} from 'vuex'  export default {    computed: {      ...mapGetters(['totalSize', 'completeSize']),      checkAll: {        get () { // 决定是否勾选          return this.$store.getters.isAllSelect        },        set (value) {// 点击了全选checkbox  value是当前checkbox的选中状态(true/false)          // this.selectAll(value)          this.$store.dispatch('selectAll', value)        }      },    },    methods: {      deleteAllCompleted () {        if(window.confirm('确定清除已完成的吗?')) {          // this.deleteCompleteTodos()          this.$store.dispatch('deleteCompleteTodos')        }      }    }  }</script><style>  .todo-footer {    height: 40px;    line-height: 40px;    padding-left: 6px;    margin-top: 5px;  }  .todo-footer label {    display: inline-block;    margin-right: 20px;    cursor: pointer;  }  .todo-footer label input {    position: relative;    top: -1px;    vertical-align: middle;    margin-right: 5px;  }  .todo-footer button {    float: right;    margin-top: 5px;  }</style>

TodoHeader.vue

<template>  <div class="todo-header">    <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="inputTodo" @keyup.enter="add"/>  </div></template><script>  export default {    data () {      return {        inputTodo: ''  // 不需要使用vuex管理(只有当前组件使用)      }    },    methods: {      add () {        // 得到输入的数据        const inputTodo = this.inputTodo.trim()        // 检查合法性        if(!inputTodo) {          alert('必须输入')          return        }        // 封装一个todo对象        const todo = {          title: inputTodo,          complete: false        }        // 添加到todos中显示        // this.addTodo(todo)        this.$store.dispatch('addTodo', todo)        // 清除输入        this.inputTodo = ''      }    }  }</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>

TODOItem.vue

<template>  <li :style="{background: bgColor}" @mouseenter="handleEnter(true)" @mouseleave="handleEnter(false)">    <label>      <input type="checkbox" v-model="todo.complete"/>      <span>{{todo.title}}</span>    </label>    <button class="btn btn-danger" v-show="isShow" @click="deleteItem">删除</button>  </li></template><script>  export default {    props: {// 指定属性名和属性值的类型      todo: Object,      index: Number    },    data () {      return {        bgColor: 'white',        isShow: false      }    },    methods: {      handleEnter (isEnter) {        if(isEnter) { // 进入          this.bgColor = '#cccccc'          this.isShow = true        } else { // 离开          this.bgColor = '#ffffff'          this.isShow = false        }      },      deleteItem () {        // this.deleteTodo(this.index)        this.$store.dispatch('deleteTodo', this.index)      }    }  }</script><style>  li {    list-style: none;    height: 36px;    line-height: 36px;    padding: 0 5px;    border-bottom: 1px solid #ddd;  }  li label {    float: left;    cursor: pointer;  }  li label li input {    vertical-align: middle;    margin-right: 6px;    position: relative;    top: -1px;  }  li button {    float: right;    display: none;    margin-top: 3px;  }  li:before {    content: initial;  }  li:last-child {    border-bottom: none;  }</style>

TodoList.vue

<template>  <ul class="todo-main">    <TodoItem v-for="(todo, index) in todos" :key="index"              :todo="todo" :index="index"/>  </ul></template><script>  import {mapState} from 'vuex'  import TodoItem from './TodoItem.vue'  import storageUtils from '../utils/storageUtils'  export default {    computed: {      ...mapState(['todos'])    },    watch: {      todos: {        deep: true, // 深度监视        handler: storageUtils.saveTodos,      }    },    components: {      TodoItem    }  }</script><style>  .todo-main {    margin-left: 0px;    border: 1px solid #ddd;    border-radius: 2px;    padding: 0px;  }  .todo-empty {    height: 40px;    line-height: 40px;    border: 1px solid #ddd;    border-radius: 2px;    padding-left: 5px;    margin-top: 10px;  }</style>

actions.js

/*包含n个用于间接更新状态的方法的对象模块 */import {ADD_TODO, DELETE_TODO, SELECT_ALL_TODOS, DELETE_COMPLETE_TODOS} from './mutation-types'export default {  addTodo ({commit}, todo) {    // 提交一个comutation请求    commit(ADD_TODO, {todo}) // 传递给mutation的是一个包含数据的对象  },  deleteTodo ({commit}, index) {    commit(DELETE_TODO, {index})  },  selectAll ({commit}, isCheck) {    commit(SELECT_ALL_TODOS, {isCheck})  },  deleteCompleteTodos ({commit}) {    commit(DELETE_COMPLETE_TODOS)  }}

getters.js

/*包含n个基于state的getter计算属性方法的对象模块 */export default {  // 总数量  totalSize (state) {    return state.todos.length  },  // 完成的数量  completeSize (state) {    return state.todos.reduce((preTotal, todo) => preTotal + (todo.complete?1:0) ,0)  },  // 判断是否需要全选  isAllSelect (state, getters) {    return getters.completeSize===getters.totalSize && getters.completeSize>0  }}

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})

mutation-types.js

/*包含n个mutation名称常量 */export const ADD_TODO = 'add_todo' // 添加todoexport const DELETE_TODO = 'delete_todo' // 删除todoexport const SELECT_ALL_TODOS = 'select_all_todos' // 全选/全不选todosexport const DELETE_COMPLETE_TODOS = 'delete_complete_todos' // 删除所有选中的

mutations.js

/*包含n个用于直接更新状态的方法的对象模块 */import {ADD_TODO, DELETE_TODO, SELECT_ALL_TODOS, DELETE_COMPLETE_TODOS} 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)  },  [SELECT_ALL_TODOS] (state, {isCheck}) {    state.todos.forEach(todo => todo.complete = isCheck)  },  [DELETE_COMPLETE_TODOS] (state) {    state.todos = state.todos.filter(todo => !todo.complete)  }}

state.js

/*状态对象模块 */import storageUtils from '../utils/storageUtils'export default {  todos: storageUtils.readTodos()}

storageUtils.js

/*向local中存储数据的工具模块1. 向外暴露一个函数(功能)   只有一个功能需要暴露2. 向外暴露一个对象(包含多个功能)   有多个功能需要暴露 */const TODOS_KEY = 'todos_key'export default {  readTodos () {    return JSON.parse(localStorage.getItem(TODOS_KEY) || '[]')  },  saveTodos (todos) {    localStorage.setItem(TODOS_KEY, JSON.stringify(todos))  }}/*export function xxx() {}export function yyy () {}*/

App.vue

<template>  <div class="todo-container">    <div class="todo-wrap">      <TodoHeader/>      <TodoList/>      <TodoFooter/>    </div>  </div></template><script>  import TodoHeader from './components/TodoHeader.vue'  import TodoList from './components/TodoList.vue'  import TodoFooter from './components/TodoFooter.vue'  export default {    components: {      TodoHeader,      TodoList,      TodoFooter    }  }</script><style>  .todo-container {    width: 600px;    margin: 0 auto;  }  .todo-container .todo-wrap {    padding: 10px;    border: 1px solid #ddd;    border-radius: 5px;  }</style>

base.css

body {  background: #fff;}.btn {  display: inline-block;  padding: 4px 12px;  margin-bottom: 0;  font-size: 14px;  line-height: 20px;  text-align: center;  vertical-align: middle;  cursor: pointer;  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);  border-radius: 4px;}.btn-danger {  color: #fff;  background-color: #da4f49;  border: 1px solid #bd362f;}.btn-danger:hover {  color: #fff;  background-color: #bd362f;}.btn:focus {  outline: none;}

main.js

/*入口JS */import Vue from 'vue'import App from './App.vue'import store from './store'import './base.css'// 创建vm/* eslint-disable no-new */new Vue({  el: '#app',  components: {App}, // 映射组件标签  template: '<App/>', // 指定需要渲染到页面的模板  store // 所有的组件对象都多了一个属性: $store(值就是store对象)})

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK