Easay / issuesSets

🏷🍨 包含JS、HTML、CSS常见面试题集锦,每天进步亿点点。
9 stars 2 forks source link

【Vue】Vuex学习笔记 #127

Open Easay opened 3 years ago

Easay commented 3 years ago

什么是Vuex?

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

一、开始

每个Vuex应用的核心是一个store(仓库)。"store"是一个容器,包含着应用中的大部分状态。

与单纯的全局对象不同

🌰:一个简单的计数应用

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

Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})
// 通过store.commit来触发状态的变更
store.commit('increment')
console.log(store.state.count) // -> 1

要想在vue组件中访问this.$store,需要为Vue实例提供创建好的store。

new Vue({
    el: '#app',
    store: store,
})

二、State

Vuex使用单一状态树。用一个对象就包含全部的应用层级状态。每个应用仅包含一个store实例。存储在Vuex中的数据和Vue实例中的data遵循相同的规则。

在vue组件中获得vuex状态 最简单的方法是使用计算属性返回某个状态:

const Counter = {
    template: `<div>{{ count }}</div>`,
    computed: {
        count(){
            return store.state.count
        }
    }
}

这种模式导致组件依赖全局状态单例。 Vuex通过store选项,提供了一种机制将状态从根组件“注入”到每个子组件中。通过在根实例中注册store,会将store注入到根组件下的所有子组件中。且子组件都能通过this.$store访问到。

const Counter = {
    template: `<div>{{ count }}</div>`,
    computed: {
        count(){
            return this.$store.state.count
        }
    }   
}

三、mapState辅助函数

当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用mapState辅助函数帮助我们生成计算属性。

// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

export default {
  // ...
  computed: {
      localComputed(){ },
      ...mapState({
        // 箭头函数可使代码更简练
        count: state => state.count,

        // 传字符串参数 'count' 等同于 `state => state.count`
        countAlias: 'count',

        // 为了能够使用 `this` 获取局部状态,必须使用常规函数
        countPlusLocalState (state) {
            return state.count + this.localCount
        }
    })
  }
}

四、Getter

帮助我们从store中的state中派生出一些状态,例如对列表进行过滤并计数:

computed: {
  doneTodosCount () {
    return this.$store.state.todos.filter(todo => todo.done).length
  }
}

vuex允许我们在store中定义getter,接受state作为其第一个参数:

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

通过属性访问

Getter会被暴露为store.getters对象,可以以属性的形式访问这些值:

store.getters.doneTodos

Getter可以接受其他getter作为第二个参数:

getters: {
  // ...
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}

通过方法访问

可以通过让getter返回一个函数,来实现给getter传参。在对store里的数组进行查询时非常有用。

getters: {
  // ...
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }

两者区别

五、mapGetters辅助函数

export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // 如果想给getter属性另取一个名字:
      doneCount: 'doneTodosCount'
    ])
  }
}
Easay commented 3 years ago

六、Mutation

Vuex的store中状态改变的唯一方法是提交mutation,类似于事件:每个mutation都有一个字符串的事件类型和一个回调函数。该回调函数是实际进行状态更改的地方,并且它会接受state作为第一个参数:

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})

⚠️:不能直接调用mutation handler,这个选项更像是事件注册:“当触发一个类型为incrementmutation时,调用此函数”。

store.commit('increment')

提交载荷

可以向store.commit传入额外的参数,就是mutation的载荷(payload):

// ...
mutations: {
  increment (state, n) {
    state.count += n
  }
}
store.commit('increment', 10)

🌟 多数情况下,载荷是一个对象。

// ...
mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
store.commit('increment', {
     amount: 10
})

对象风格的提交方式

store.commit({
  type: 'increment',
  amount: 10
})
mutations:  {
      increment (state, payload) {
          state.count += payload.amount
      }
}

注意事项

1.最好提前在store中初始化好所有所需的属性; 2.当需要在对象上添加新属性时,应该:

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
  state: { ... },
  mutations: {
    // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
    [SOME_MUTATION] (state) {
      // mutate state
    }
  }
})

⚠️ Mutation必须是同步函数

组件中提交Mutation

在组件中提交:this.$store.commit('xxx'),或者使用Mutation的辅助函数mapMutations。可以直接将method中的方法映射成mutation使用。

import { mapMutations } from 'vuex'
export default {
    method: {
        ...mapMutations([
            'increment',
            'incrementBy'
        ]),
        ...mapMutations({
            add: 'increment'
        })
    }
}
Easay commented 3 years ago

Action

Action类似Mutation,不同之处在于:

Action函数接受一个与store实例具有相同方法和属性的context对象。因此,可以按下面方式使用context替代store

context.commit()
context.getters
context.state

可以使用参数结构简化:

actions: {
   increment ({ commit }) {
        commit('increment')
   }
}

分发Action

store.dispatch('increment')

可以在action内部执行异步操作:

actions: {
   incrementAsync ({ commit }) {
        setTimeout(()=>{
              commit('increment')
        }, 1000)
   }
}

购物车实例

actions: {
      checkout({ commit, state }, products) {
          // 备份当前购物车的物品
          const savedCartItems = [...state.cart.added]
          // 发出结账请求,然后清空购物车
          commit(types.CHECKOUT_REQUEST)
          // 购物API接受一个成功回调和一个失败回调
          shop.buyProducts(
              products,
              // 成功操作
              () => commit(types.CHECKOUT_SUCCESS),
              // 失败操作
              () => commit(types.CHECKOUT_FAILURE, savedCartItems)
          )
      }
  }
Easay commented 3 years ago

Module

当应用非常复杂时,store对象可能变得相当臃肿,为了解决以上问题,vuex允许将store分成模块。每个模块拥有自己的state等内容。

const moduleA = {
  state: () => ({
    count: 0
  }),
  mutations: {
    increment (state) {
      // 这里的 `state` 对象是模块的局部状态
      state.count++
    }
  },

  getters: {
    // 根节点状态会作为第三个参数暴露
    doubleCount (state, getters, rootState) {
      return state.count * 2
    }
  },
   // 局部状态通过context.state暴露,根节点的状态为context.rootState
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}
const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

命名空间

默认情况下,模块内部的action、mutation和getter是注册在全局命名空间的。

可以通过添加namespaced: true的方式使其成为带命名空间的模块。