zxpnet网站 zxpnet网站
首页
前端
后端服务器
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

zxpnet

一个爱学习的java开发攻城狮
首页
前端
后端服务器
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 大前端课程视频归档
  • html

  • js

  • 前端框架

  • 自动化构建

  • typescript

  • es6

  • bootstrap

  • layer

  • vue

  • vue3

    • 邂逅Vue3和Vue3开发体验
    • Vue3基础语法
    • Vue3组件化开发
    • Vue3过渡&动画实现
    • Babel和devServer
    • Composition API
    • vue3高级语法
    • vue3源码
    • vue-router路由
    • vuex状态管理
      • 什么是状态管理
        • 复杂的状态管理
        • Vuex的状态管理
      • vuex
        • 安装vuex
        • 浏览器安装Vue devtool
      • Store
        • A、创建和使用Store
        • B、单一状态树
      • 一、state状态
        • 在setup中使用mapState
      • 二、getters的基本使用
        • 使用
      • 三、mutations
        • 1、Mutation携带数据
        • 提交的方式
        • Mutation常量类型
        • mapMutations辅助函数
        • mutation重要原则
      • 四、actions的基本使用
        • actions的辅助函数
        • actions的异步操作
      • 五、module的基本使用
        • 什么是Module
        • module的局部状态
        • module的命名空间
        • module修改或派发根组件
        • module的辅助函数
      • nexttick
      • historyApiFallback
      • ide模板
    • vue开源项目
    • vue3-cms项目笔记
    • pinia状态管理
  • vuepress

  • hexo博客

  • 文档

  • biz业务

  • frontend
  • vue3
shollin
2022-02-28
目录
什么是状态管理
复杂的状态管理
Vuex的状态管理
vuex
安装vuex
浏览器安装Vue devtool
Store
A、创建和使用Store
B、单一状态树
一、state状态
在setup中使用mapState
二、getters的基本使用
使用
三、mutations
1、Mutation携带数据
提交的方式
Mutation常量类型
mapMutations辅助函数
mutation重要原则
四、actions的基本使用
actions的辅助函数
actions的异步操作
五、module的基本使用
什么是Module
module的局部状态
module的命名空间
module修改或派发根组件
module的辅助函数
nexttick
historyApiFallback
ide模板

vuex状态管理

  • 什么是状态管理
    • 复杂的状态管理
    • Vuex的状态管理
  • vuex
    • 安装vuex
    • 浏览器安装Vue devtool
  • Store
    • A、创建和使用Store
    • B、单一状态树
  • 一、state状态
  • 二、getters的基本使用
    • 使用
  • 三、mutations
    • 1、Mutation携带数据
    • 提交的方式
    • Mutation常量类型
    • mapMutations辅助函数
    • mutation重要原则
  • 四、actions的基本使用
    • actions的辅助函数
    • actions的异步操作
  • 五、module的基本使用
    • 什么是Module
    • module的局部状态
    • module的命名空间
    • module修改或派发根组件
    • module的辅助函数
  • nexttick
  • historyApiFallback
  • ide模板

# 什么是状态管理

在开发中,我们会的应用程序需要处理各种各样的数据,这些数据需要保存在我们应用程序中的某一个位置,对于这些数据

的管理我们就称之为是 状态管理。

n 在前面我们是如何管理自己的状态呢?

  • 在Vue开发中,我们使用组件化的开发方式;而在组件中我们定义data或者在setup中返回使用的数据,这些数据我们称之为state;

  • 在模块template中我们可以使用这些数据,模块最终会被渲染成DOM,我们称之为View;

  • 在模块中我们会产生一些行为事件,处理这些行为事件时,有可能会修改state,这些行为事件我们称之为actions;

image-20220228153822331

# 复杂的状态管理

1、JavaScript需要管理的状态越来越多,越来越复杂;

  • 这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等;

  • 也包括一些UI的状态,比如某些元素是否被选中,是否显示加载动效,当前分页;

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

  • 多个视图依赖于同一状态;

  • 来自不同视图的行为需要变更同一状态;

3、 我们是否可以通过组件数据的传递来完成呢?

  • 对于一些简单的状态,确实可以通过props的传递或者Provide、inject的方式来共享状态;

  • 但是对于复杂的状态管理来说,显然单纯通过传递和共享的方式是不足以解决问题的,比如兄弟组件如何共享数据呢?

# Vuex的状态管理

管理不断变化的state本身是非常困难的:

  • 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也有可能会引起状态的变化;

  • 当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪;

因此,我们是否可以考虑将组件的内部状态抽离出来,以一个全局单例的方式来管理呢?

  • 在这种模式下,我们的组件树构成了一个巨大的 “视图View”;

  • 不管在树的哪个位置,任何组件都能获取状态或者触发行为;

  • 通过定义和隔离状态管理中的各个概念,并通过强制性的规则来维护视图和状态间的独立性,我们的代码边会变得更加结构化和易于维护、跟踪;

这就是Vuex背后的基本思想,它借鉴了Flux、Redux、Elm(纯函数语言,redux有借鉴它的思想)

image-20220228154648667

流程:

状态数据保存在state里面,所有的组件可以通过$store.state.XXX获取到,当组件要修改数据时,可以commit提交给mutations,mutations里面有很多函数,进行修改state里面的数据。 注意mutations不能处理异步数据的请求,如果要异步请求数据时,就用actions

# vuex

# 安装vuex

npm install vuex@next
1

# 浏览器安装Vue devtool

vue其实提供了一个devtools,方便我们对组件或者vuex进行调试: 我们需要安装beta版本支持vue3,目前是6.0.0 beta15 (2022年),它有两种常见的安装方式:

  • 方式一:通过chrome的商店;

  • 方式二:手动下载代码,编译、安装;

方式一:通过Chrome商店安装

方式二:手动下载代码,编译、安装https://github.com/vuejs/devtools/tree/v6.0.0-beta.15下载代码;

执行 yarn install 安装相关的依赖;执行 yarn run build 打包;

import { createStore } from "vuex"
import home from './modules/home'
import user from './modules/user'

const store = createStore({
  state() {
    return {
      rootCounter: 100
    }
  },
  getters: {
    doubleRootCounter(state) {
      return state.rootCounter * 2
    }
  },
  mutations: {
    increment(state) {
      state.rootCounter++
    }
  },
  modules: {
    home,
    user
  }
});

export default store;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# Store

# A、创建和使用Store

每一个Vuex应用的核心就是store(仓库):store本质上是一个容器,它包含着你的应用中大部分的状态(state);

Vuex和单纯的全局对象有什么区别呢?

第一:Vuex的状态存储是响应式的, 当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会被更新;

n 第二:你不能直接改变store中的状态

  • 改变store中的状态的唯一途径就显示提交 (commit) mutation;

  • 这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够通过一些工具帮助我们更好的管理应用的状态;

使用步骤:

  • 创建Store对象;

  • app中通过插件安装;

在组件中使用store,我们按照如下的方式:

  • 在模板中使用;

  • 在options api中使用,比如computed;

  • 在setup中使用;

# B、单一状态树

1、Vuex 使用单一状态树:

  • 用一个对象就包含了全部的应用层级的状态;

  • 采用的是SSOT,Single Source of Truth,也可以翻译成单一数据源;

  • 这也意味着,每个应用将仅仅包含一个 store 实例;

  • 单状态树和模块化并不冲突,后面我们会讲到module的概念;

2、单一状态树的优势:

  • 如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难;所以Vuex也使用了单一状态树来管理应用层级的全部状态;

  • 单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护;

# 一、state状态

1、模板里面直接使用$store.state.XXX this.$store.state.count++;

2、计算属性:如果觉得那种方式有点繁琐(表达式过长),我们可以使用计算属性:

3、如果我们有很多个状态都需要获取话,可以使用mapState辅助函数:

  • mapState的方式一:对象类型;

  • mapState的方式二:数组类型;

  • 也可以使用展开运算符和来原有的computed混合在一起;

import { mapState } from 'vuex'

  export default {
    computed: {
      fullName() {
        return "Kobe Bryant"
      },
      // 其他的计算属性, 从state获取
      // 参数为数组 ...mapState(["counter", "name", "age", "height"])
      ...mapState({
        sCounter: state => state.counter,
        sName: state => state.name
      })
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 在setup中使用mapState

在setup中如果我们单个获取装是非常简单的:

  • 通过useStore拿到store后去获取某个状态即可;
  • 但是如果我们需要使用 mapState 的功能呢? 默认情况下,Vuex并没有提供非常方便的使用mapState的方式,这里我们进行了一个函数的封装:
  • mapState()函数,返回的是一个对象,对象里面是函数,可以用es6解构出来(computed里面,放的就是函数),里面的参数可以是数组,也可以是对象
  • 获取mapState里面的数据,实际上是调用this.$store.state.XXX, 在setup里面是没有this的,因此需要bind()自身
import { mapState, useStore } from 'vuex'
  import { computed } from 'vue'

  export default {
    computed: {
      fullName: function() {
        return "1fdasfdasfad"
      },
      ...mapState(["name", "age"])
    },

    setup() {
      const store = useStore()
      const sCounter = computed(() => store.state.counter)
      // const sName = computed(() => store.state.name)
      // const sAge = computed(() => store.state.age)

      const storeStateFns = mapState(["counter", "name", "age", "height"])

      // {name: function, age: function, height: function}
      // {name: ref, age: ref, height: ref}
      const storeState = {}
      Object.keys(storeStateFns).forEach(fnKey => {
        const fn = storeStateFns[fnKey].bind({$store: store}) // 需要绑定$store,不然函数里面的this拿不到数据
        storeState[fnKey] = computed(fn)
      })

      return {
        sCounter,
        ...storeState
      }
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

userMapper.js

import { computed } from 'vue'
import { useStore } from 'vuex'

export function useMapper(mapper, mapFn) {
  // 拿到store独享
  const store = useStore()

  // 获取到对应的对象的functions: {name: function, age: function}
  const storeStateFns = mapFn(mapper)

  // 对数据进行转换
  const storeState = {}
  Object.keys(storeStateFns).forEach(fnKey => {
    const fn = storeStateFns[fnKey].bind({$store: store})
    storeState[fnKey] = computed(fn)
  })

  return storeState
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

useState.js

import { mapState, createNamespacedHelpers, useStore } from 'vuex'
import { useMapper } from './useMapper'
import { computed } from 'vue'

export function useState(moduleName, mapper) {
  let mapperFn = mapState
  if (typeof moduleName === 'string' && moduleName.length > 0) {
    mapperFn = createNamespacedHelpers(moduleName).mapState
  } else {
    mapper = moduleName
  }

  return useMapper(mapper, mapperFn)
}

export function useState2(mapper) {
  // 拿到store独享
  const store = useStore();

  // 获取到对应的对象的functions: {name: function, age: function}
  const storeStateFns = mapState(mapper)

  // 对数据进行转换
  const storeState = {}
  Object.keys(storeStateFns).forEach(fnKey => {
    const fn = storeStateFns[fnKey].bind({$store: store})
    storeState[fnKey] = computed(fn)
  })

  return storeState
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 二、getters的基本使用

某些属性我们可能需要经过变化后来使用,这个时候可以使用getters,相当于computed计算属性

const homeModule = {
  namespaced: true,
  state() {
    return {
      homeCounter: 100
    }
  },
  getters: {
    doubleHomeCounter(state, getters, rootState, rootGetters) {
      return state.homeCounter * 2
    },
    otherGetter(state) {
      return 100
    }
  },
  mutations: {
    increment(state) {
      state.homeCounter++
    }
  },
  actions: {
    incrementAction({commit, dispatch, state, rootState, getters, rootGetters}) {
      commit("increment")
      commit("increment", null, {root: true})

      // dispatch
      // dispatch("incrementAction", null, {root: true})
    }
  }
}

export default homeModule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

参数:

  • state
  • getters 可以调用另一个getters函数,本身不能接收参数,可以通过返回一个带参函数来获取参数
  • rootState,
  • rootGetters

getters中的函数本身,可以返回一个函数,那么在使用的地方相当于可以调用这个函数:

totalPriceCountGreaterN(state, getters) {
      return function(n) { // 返回一个函数,参数N
        let totalPrice = 0
        for (const book of state.books) {
          if (book.count > n) {
            totalPrice += book.count * book.price
          }
        }
        return totalPrice * getters.currentDiscount
      }
},
1
2
3
4
5
6
7
8
9
10
11

# 使用

  • 在template里面,直接用$store.getters.XXX
  • 在计算属性里面, 用mapGetters辅助函数,参数可以是数组或者对象
  • 在setup里面,使用computed()函数,需要绑定store,同mapState用法

# 三、mutations

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation:

# 1、Mutation携带数据

很多时候我们在提交mutation的时候,会携带一些数据,这个时候我们可以使用参数payload来接收,可以是数值或者对象

# 提交的方式

1、 this.$store.commit('increment',{n:10})

2、提交一个对象,用type来指明mutations里面的方法

methods: {
      addTen() {
        // this.$store.commit('incrementN', 10)
        // this.$store.commit('incrementN', {n: 10, name: "why", age: 18})
        // 另外一种提交风格
        this.$store.commit({
          type: INCREMENT_N,
          n: 10, 
          name: "why", 
          age: 18
        })
      }
 }
1
2
3
4
5
6
7
8
9
10
11
12
13

# Mutation常量类型

在vuex里面定义的mutation函数,commit的时候,函数名要保持一致,可以抽取一个常量,定义常量:mutation-type.js

image-20220302160050827

# mapMutations辅助函数

 import { mapMutations, mapState } from 'vuex'

  import { INCREMENT_N } from '../store/mutation-types'

  export default {
    methods: {
      ...mapMutations(["increment", "decrement", INCREMENT_N]),
      ...mapMutations({
        add: "increment"
      })
    },
    setup() {
      const storeMutations = mapMutations(["increment", "decrement", INCREMENT_N])

      return {
        ...storeMutations
      }
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# mutation重要原则

一条重要的原则就是要记住 mutation 必须是同步函数这是因为devtool工具会记录mutation的日记;

  • 每一条mutation被记录,devtools都需要捕捉到前一状态和后一状态的快照;

  • 但是在mutation中执行异步操作,就无法追踪到数据的变化;

  • 所以Vuex的重要原则中要求 mutation必须是同步函数;

# 四、actions的基本使用

Action类似于mutation,不同在于:

  • Action提交的是mutation,而不是直接变更状态;

  • Action可以包含任意异步操作;

这里有一个非常重要的参数context:

  • context是一个和store实例均有相同方法和属性的context对象;
  • 所以我们可以从其中获取到commit方法来提交一个mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters;
  • 但是为什么它不是store对象呢?这个等到我们讲Modules时再具体来说
  • context对象,包括:commit, dispatch, state, rootState, getters, rootGetters
actions: {
    incrementAction({commit, dispatch, state, rootState, getters, rootGetters}, ) {
      commit("increment")
      commit("increment", null, {root: true})

      // dispatch
      // dispatch("incrementAction", null, {root: true})
    }
}

export default {
    methods: {
      increment() {
        this.$store.dispatch("incrementAction", {count: 100})
      },
      decrement() {
        // 3.派发风格(对象类型)
        this.$store.dispatch({
          type: "decrementAction"
        })
      }
    },
    mounted() {
      this.$store.dispatch("getHomeMultidata")
    },
    setup() {
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# actions的辅助函数

  import { mapActions } from 'vuex'

  export default {
    methods: {
      // ...mapActions(["incrementAction", "decrementAction"]),
      // ...mapActions({
      //   add: "incrementAction",
      //   sub: "decrementAction"
      // })
    },
    setup() {
      const actions = mapActions(["incrementAction", "decrementAction"])
      const actions2 = mapActions({
        add: "incrementAction",
        sub: "decrementAction"
      })

      return {
        ...actions,
        ...actions2
      }
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# actions的异步操作

Action 通常是异步的,那么如何知道 action 什么时候结束呢?

  • 我们可以通过让action返回Promise,在Promise的then中来处理完成后的操作;
import { onMounted } from "vue";
  import { useStore } from 'vuex'

  export default {
    setup() {
      const store = useStore()

      onMounted(() => {
        const promise = store.dispatch("getHomeMultidata")
        promise.then(res => {
          console.log(res)
        }).catch(err => {
          console.log(err)
        })
      })
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Promise - JavaScript | MDN (mozilla.org) (opens new window)

# 五、module的基本使用

# 什么是Module

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

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module); 每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块;

image-20220303085216643

# module的局部状态

对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象:

image-20220303085447672

# module的命名空间

默认情况下,模块内部的action和mutation仍然是注册在全局的命名空间中的:

  • 这样使得多个模块能够对同一个 action 或 mutation 作出响应;

  • Getter同样也默认注册在全局命名空间;

如果我们希望模块具有更高的封装度和复用性,可以添加 namespaced: true 的方式使其成为带命名空间的模块:

  • 当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名;
<template>
  <div>
    <h2>root:{{ $store.state.rootCounter }}</h2>
    <h2>home:{{ $store.state.home.homeCounter }}</h2>
    <h2>user:{{ $store.state.user.userCounter }}</h2>

    <hr>
    <h2>{{ $store.getters["home/doubleHomeCounter"] }}</h2>

    <button @click="homeIncrement">home+1</button>
    <button @click="homeIncrementAction">home+1</button>
  </div>
</template>

<script>
  export default {
    methods: {
      homeIncrement() {
        this.$store.commit("home/increment")
      },
      homeIncrementAction() {
        this.$store.dispatch("home/incrementAction")
      }
    }
  }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# module修改或派发根组件

如果我们希望在action中修改root中的state,那么有如下的方式:

actions: {
    incrementAction({commit, dispatch, state, rootState, getters, rootGetters}) {
      commit("increment")
      commit("increment", null, {root: true}) // 提交给根mutations

      // dispatch 
      // dispatch("incrementAction", null, {root: true})
    }
}
1
2
3
4
5
6
7
8
9

# module的辅助函数

如果辅助函数有三种使用方法:

  • 方式一:通过完整的模块空间名称来查找;

  • 方式二:第一个参数传入模块空间名称,后面写上要使用的属性;

  • 方式三:通过 createNamespacedHelpers 生成一个模块的辅助函数;

image-20220303105951473

import { createNamespacedHelpers } from "vuex"; //  mapState, mapGetters, mapMutations, mapActions

const { mapState, mapGetters, mapMutations, mapActions } = createNamespacedHelpers("home"); // 创建命名空间对应的对象,包含mapState等



1
2
3
4
5
6

# nexttick

官方解释:将回调推迟到下一个 DOM 更新周期之后执行。在更改了一些数据以等待 DOM 更新后立即使用它。

比如我们有下面的需求:

点击一个按钮,我们会修改在h2中显示的message; message被修改后,获取h2的高度;

n 实现上面的案例我们有三种方式:

  • 方式一:在点击按钮后立即获取到h2的高度(错误的做法)

  • 方式二:在updated生命周期函数中获取h2的高度(但是其他数据更新,也会执行该操作)

  • 方式三:使用nexttick函数;

const addMessageContent = () => {
        message.value += "哈哈哈哈哈哈哈哈哈哈"

        // 更新DOM后,再执行里面的内容
        nextTick(() => {
          console.log(titleRef.value.offsetHeight)
        })
}
1
2
3
4
5
6
7
8

nexttick是如何做到的呢?

Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。浏览器的事件循环(event loop),像watch、updated等操作会放在微任务队列

Vue.nextTick 的原理和用途 - 知乎 (zhihu.com) (opens new window)

# historyApiFallback

historyApiFallback是开发中一个非常常见的属性,它主要的作用是解决SPA页面在路由跳转之后,进行页面刷新时,返回404的错误。

  • boolean值:默认是false

  • 如果设置为true,那么在刷新时,返回404错误时,会自动返回 index.html 的内容;

  • object类型的值,可以配置rewrites属性:

p可以配置from来匹配路径,决定要跳转到哪一个页面;

事实上devServer中实现historyApiFallback功能是通过connect-history-api-fallback库的:

可以查看connect-history-api-fallback 文档

image-20220303165847598

// vue.config.js 里面的配置会合并到webpack.config.js配置里面

module.exports = {
  configureWebpack: {
    devServer: {
      // historyApiFallback: true
    }
  }
}

1
2
3
4
5
6
7
8
9
10

# ide模板

vuex

vsetup

import {ref, computed, onMounted} from 'vue';
import { createNamespacedHelpers, useStore} from 'vuex';
    
setup() {
            const store = useStore();
            // 初始化
            onMounted(()=>{
                store.dispatch("goods/init");
            });

            // 拿到vuex里面的state、getters、mutations、actions
            let {mapState, mapMutations, mapActions, mapGetters} = createNamespacedHelpers("goods");

            let storeStateFns =  mapState(['cate',"goods","goodsList",]); // 拿模块里面的state
            let stateObj={};
            Object.keys(storeStateFns).forEach( key=>{
                stateObj[key] = computed( storeStateFns[key].bind({"$store":store}) ); // 需要绑定$store
            });

            let storeMutations =  mapMutations(['setGoodsList']); // 拿goods模块里面的mutaions

            return {
            	...stateObj
                ...storeMutations,
            }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
vue-router路由
vue开源项目

← vue-router路由 vue开源项目→

最近更新
01
国际象棋
09-15
02
成语
09-15
03
自然拼读
09-15
更多文章>
Theme by Vdoing | Copyright © 2019-2025 zxpnet | 粤ICP备14079330号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式