Day43:VUEX

1. 基本介绍

这里介绍的VueX是匹配Vue3的V4版本,它绝大多数功能使用都和之前基本保持一致。

1.1 官方定义

先一起看一下官网对于VueX的定义:

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

1.2 状态管理

官网定义中提到了一个现代前端中非常重要的概念状态管理 ,怎么来理解这个概念呢?

  • 在Vue中我们会定义一些驱动应用的响应式数据,我们把这些数据叫做状态(state)
  • 定义的初始化数据会按照Vue的模板语法以声明式的方式映射到视图(view)
  • 用户在视图上的操作(actions)会引起状态变化从而导致视图刷新

在复杂的应用中,我们会经常遇到以下的问题:

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

其实这些问题我们也能解决,但需要状态在组件之间反复传递,十分容易出现错误,开发和维护成本都非常高,我们为什么不把组件的共享状态抽取出来,放到一个单独的空间管理呢,这个空间可以被任意组件访问到,这其实就是VueX的设计思想

1.3 VueX的必要性

并不是所有的项目中都需要使用VueX,如果你不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果你的应用够简单,你最好不要使用 Vuex。但是如果你需要构建一个中大型应用,在组件外部管理状态往往是一个更好的选择,那么这个时候VueX便会是自然而然的选择了。

1.4 项目安装

通过npm命令在项目中安装VueX

npm i vuex --save

在package.json中确认正确的版本号

1.5 基本使用

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  2. 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

安装好VueX后,在src目录下新建一个store文件夹,再建立一个index.js文件,我们在这个js文件中来创建我们的store容器。

import { createStore } from 'vuex'

// 创建一个新的 store 实例
const store = createStore({
  state() {
    return {
      count: 0,
    }
  },
  mutations: {
    increment(state) {
      state.count++
    },
  },
})
export default store

然后在入口文件main.js中引入store并使用它。这里的store和之前的router都可以理解为Vue的插件,都可以调用use方法。

import { createApp } from 'vue'
import App from './App.vue'
import store from './store'

createApp(App).use(store).mount('#app')

好了,现在我们的项目中就可以通过VueX来进行全局的状态管理了,接下来我们要深入学习其中的核心概念!

vue2 index.js

npm i vuex@3

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

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  store:store, // 可以简写
}).$mount('#app')
import Vue  from 'vue'
import VueX  from 'vuex'
Vue.use(VueX)
const store = new VueX.Store({
    state(){
        return {
            count: 1,
            msg:'公共状态'
        }
    },
    mutations: {
        // 定义修改数据的方法
        addCount(state, payload) {
            state.count += payload.a
        }
    }
})
export default store

2.state

2.1 保存状态

store中保存的状态同样是具有响应性的,我们把应用中的公共状态统一保存到state属性中。

js

const store = createStore({
  state() {
    return {
      count: 0, //这里的count后面就可以被应用中所有的组件共享
    }
  },
})

2.2 读取状态

在项目中任意创建了一个Hello组件,现在希望在这个组件中取出刚才的count。由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:

在组件中获取到store必须调用useStore这个方法(和vue-router类似,在Vue3的最新生态中推荐使用函数的方式来获取示例,而非this)

vue

//Hello组件中
    
  

2.3 映射函数

映射函数,可以将公共的状态映射到当前的组件,再在当前组件中调用就会简便很多。如果你使用的是Vue2版本的配置选项写法,使用映射函数往往非常高效。当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性:

import { mapState } from 'vuex'

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

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

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

2.4 组件独立的状态

使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。

VUE2实现

Day43:VUEX_第1张图片

Day43:VUEX_第2张图片


vuex中mutations里都是同步修改数据的方法, 如果希望异步修改数据,需要把对应的方法,定义在actions的对象中

但是actions不能直接修改state,最终还是要通过触发mutations来实现。

Day43:VUEX_第3张图片

引用的方法同样可以映射


3.getter

3.1设置getters

有时候我们需要从 store 中的 state 中派生出一些状态,通过计算属性即可实现,但如果有多个组件需要用到这个计算属性,我们要么复制这个计算属性,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。

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

3.2 读取getters

和state类似,同样可以在组件中通过store实例来读取到对应的getter。

//Hello中
let doneTodos = computed(()=>{
  return useStore().getters.doneTodos
})

3.3 映射函数

mapGetters 辅助函数同样可以将 store 中的 getter 映射到局部计算属性中,如果你使用配置选项仍然推荐你这么做,在setup语法糖中这样使用的意义就没那么大了。

import { mapGetters } from 'vuex'
export default {
  computed: {
    // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
    ])
  }
}

VUE2实现

4.mutation

4.1 提交commit

VueX规定必须通过mutation来能修改state中的状态。mutations中保存了修改了state的各种方法,在组件中可以通过store.commit来触发对应的方法,同时传入参数(这个参数叫做载荷)

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

  

4.2 mutation必须是同步函数

VueX是规定mutations中修改状态必须是同步的,我们可以尝试写一段异步代码:

mutations: {
  increment(state, n) {
    setTimeout(() => {
      state.count += n
    }, 2000)
  },
},

大家肯定会疑问,代码没问题呀页面也能够正确响应状态的变化,但是VueX设计思想中需要能够捕捉到mutation前后状态的快照,异步代码无法让VueX能够了解其执行时机,这样描述可能大家此时仍然不好理解,所以现在你需要记住这个结论!

5.action

action和mutation有些类似,但不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

Action 函数第一个参数接受一个与 store 实例具有相同方法和属性的 context对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。

Action第二个参数也接受一个载荷。

actions:{
  increment(ctx,payload){
    setTimeout(()=>{
      ctx.commit('increment',payload)
    },2000)
  }
}

在组件中视图中,可以通过事件来触发action。所以之前的异步操作可以通过分发action来实现。

const add = () => {
  store.dispatch('increment',2)
}

6.module

6.1 嵌套模块

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = createStore({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

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

6.2 命名空间

默认情况下,模块内部的 action 和 mutation 仍然是注册在全局命名空间的——这样使得多个模块能够对同一个 action 或 mutation 作出响应。Getter 同样也默认注册在全局命名空间。必须注意,不要在不同的、无命名空间的模块中定义两个相同的 getter 从而导致错误。

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:

const store = createStore({
  modules: {
    account: {
      namespaced: true,

      // 模块内容(module assets)
      state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
      getters: {
        isAdmin () { ... } // -> getters['account/isAdmin']
      },
      actions: {
        login () { ... } // -> dispatch('account/login')
      },
      mutations: {
        login () { ... } // -> commit('account/login')
      },
      // 嵌套模块
      modules: {
        // 继承父模块的命名空间
        myPage: {
          state: () => ({ ... }),
          getters: {
            profile () { ... } // -> getters['account/profile']
          }
        },
      }
    }
  }
})

命名空间在多人协作处理大型应用的时候还是非常具有实际意义的。

vue2实现

分别为login和profile新建store项目,可以分别为不同的组件书写不同的store

import Vue from 'vue'
import VueX from 'vuex'
import loginStore from "./loginStore";
import profileStore from "./profileStore";
Vue.use(VueX)
const store = new VueX.Store({
    modules:{
        login:loginStore,
        profile:profileStore,
    }
})
export default store

在app中可以通过模板来获取到子组件store中的值

// 代表Store中modules下,login这个store中a元素的值

{{ $store.state.login.a }}

在store中,存放需要在组件中流通的公共状态

不需要流通的状态和属性还是仍然放在原本的组件中

你可能感兴趣的:(vue.js,前端)