• Vue学习—vuex


    Vuex状态管理

    1 什么是状态管理

    ​ 在开发中,我们会让应用程序需要处理各种各样的数据,这些数据需要保存在我们应用程序中的某一个位置,对于这些数据的管理我们就 称之为是状态管理

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

    ​ 在Vue开发中,我们使用组件化的开发方式:

    1. 在组件中我们定义data或者在setup中返回使用的数据,这些数 据我们称之为state;

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

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

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TS1Imi1h-1668407537860)(assets/image-20221103054848920.png)]

    2 复杂的状态管理

    ​ JavaScript开发的应用程序,已经变得越来越复杂了.JavaScript需要管理的状态越来越多,越来越复杂:

    • 这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等;
    • 也包括一些UI的状态,比如某些元素是否被选中,是否显示加载动效,当前分页;

    然而,当我们有多个组件共享一个共同的状态时,就没有这么简单了:单向数据流的简洁性很容易被破坏:

    1. 多个视图依赖于同一状态;
    2. 来自不同视图的行为需要变更同一状态;

    对于情景 1,一个可行的办法是将共享状态“提升”到共同的祖先组件上去,再通过 props 传递下来。然而在深层次的组件树结构中这么做的话,很快就会使得代码变得繁琐冗长。这会导致另一个问题:Prop 逐级透传问题

    Prop 逐级透传问题:

    ​ 通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props。想象一下这样的结构:有一些多层级嵌套的组件,形成了一颗巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KG1JAzsp-1668407537862)(assets/image-20221103053130854.png)]

    注意,虽然这里的

    组件可能根本不关心这些 props,但为了使 能访问到它们,仍然需要定义并向下传递。如果组件链路非常长,可能会影响到更多这条路上的组件。这一问题被称为“prop 逐级透传”,显然是我们希望尽量避免的情况。

    对于情景 2,我们经常发现自己会直接通过模板引用获取父/子实例,或者通过触发的事件尝试改变和同步多个状态的副本。但这些模式的健壮性都不甚理想,很容易就会导致代码难以维护。

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

    • 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也有可能会引起状态的变化;
    • 当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪;

    ​ 一个更简单直接的解决方案是抽取出组件间的共享状态,放在一个全局单例中来管理。这样我们的组件树就变成了一个大的“视图”,而任何位置上的组件都可以访问其中的状态或触发动作。通过定义和隔离状态管理中的各个概念,并通过强制性的规则来维护视图和状态间的独立性,我们的代码边会变得更加结构化和易于维护、跟踪;这就是Vuex背后的基本思想.

    3 Vuex

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qL4MfW06-1668407537863)(assets/image-20221105093013001.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cHju7cR9-1668407537865)(assets/image-20221103053957771.png)]

    每一个 Vuex 应用的核心就是 store,里面又包括:
    (1)state(数据):用来存放数据源,就是公共状态;
    (2)getters(数据加工):有的时候需要对数据源进行加工,返回需要的数据;
    (3)actions(事件):要执行的操作,可以进行同步或者异步事件
    (4)mutations(执行):操作结束之后,actions通过commit更新state数据源
    (5)modules:使用单一状态树,致使应用的全部状态集中到一个很大的对象,所以把每个模块的局部状态分装使每一个模块拥有本身的 state、mutation、action、getters、甚至是嵌套子模块;

    vuex的工作流程就是:
    (1)通过dispatch去提交一个actions,
    (2) actions接收到这个事件之后,在actions中可以执行一些异步|同步操作,根据不同的情况去分发给不同的mutations,
    (3)actions通过commit去触发mutations,
    (4)mutations去更新state数据,state更新之后,就会通知vue进行渲染。

    4.Vuex的使用

    第一步: 安装vuex

    npm install vuex
    
    • 1

    第二步: 在src目录下创建一个store目录,在该目录下创建index.js文件,用于创建Store对象

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vrZeYtHX-1668407537867)(assets/image-20221105112705487.png)]

    //用于创建Vuex的核心对象Store
    //1.导入
    import {createStore } from 'vuex'
    
    
    //2.创建Store对象
    const store = createStore({
       //state 用于存储数据
       state(){
          return {
             count:1,
          }
       },
       //actions 用于响应组件中的事件
       actions:{
    
       },
       //mutations 用于操作数据
       mutations:{
    
       }
    });
    
    
    //3.暴露出store对象
    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

    第三步: 在main.js 使用store对象

    import store from './store';
    app.use(store);
    
    • 1
    • 2

    第四步: 在 Vue 组件中, 可以通过 this.$store 访问store实例

    <template>
       <h2>{{$store.state.count}}h2>
    template>
    
    <script>
    export default {
     name: 'App',
     data() {
       return {
         
       };
     },
     components: {
     },
     methods: {
      
     },
     mounted() {
       console.log(this.$store.state.count);
     }
    };
    script>
    
    <style lang="css" scoped>
    style>
    
    • 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

    接下来,我们使用一个案例,熟悉vuex的基本使用:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WSLsKQqR-1668407537868)(assets/1.gif)]

    CalcSum.vue

    <template>
        <div>
            <h2>和:{{$store.state.sum}}h2>
            <input type="number" length="3" v-model="num"/>
            <input type="button" value="+" @click="add"/>
            <input type="button" value="-" @click="sub"/>
        div>
    template>
    
    <script>
    export default {
        name: 'CalcSum',
    
        data() {
            return {
                num:1,
            };
        },
    
        mounted() {
            
        },
    
        methods: {
            add(){
                //通过dispatch去提交一个action
                this.$store.dispatch('increment', this.num);
            },
            sub(){
                this.$store.dispatch('decrement', this.num);
            },
        },
    };
    script>
    
    <style lang="css" scoped>
    
    style>
    
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38

    App.vue

    <template>
        <CalcSum/>
    template>
    
    <script>
    import CalcSum from './components/CalcSum.vue'
    export default {
      name: 'App',
      data() {
        return {
           num:1,
        };
      },
      components: {
        CalcSum
      },
      methods: {
       
      },
      mounted() {
        console.log(this.$store.state.sum);
      }
    };
    script>
    
    <style lang="css" scoped>
    style>
    
    • 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/index.js

    //用于创建Vuex的核心对象Store
    //1.导入
    import {createStore } from 'vuex'
    
    //2.创建Store对象
    const store = createStore({
        //state 用于存储数据
        state(){
           return {
              sum:0,
           }
        },
        //actions 用于响应组件中的事件
        actions:{
            increment:function(context,value){
                //actions通过commit去触发mutations
                context.commit("INCREMENT",value)
            },
            decrement(context,value){
                context.commit("DECREMENT",value)
            }
    
        },
        //mutations 用于操作数据
        mutations:{
            //mutations去更新state数据
            INCREMENT(store,value){
                store.sum +=value;   
            },
            DECREMENT(store,value){
                store.sum -= value; 
            }
        }
    });
    
    
    //3.暴露出store对象
    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
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    注意:

    每次我们在组件获取vuex的状态数据需要写{{$store.state.属性名}},太复杂了,由于 store 中的状态是响应式的,在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。

    computed: {
       count () {
           return this.$store.state.sum
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    {{count}}
    
    • 1

    5. 核心概念

    5.1 单一状态树

    ​ Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

    单一状态树的优势:

    • 如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难;
    • 所以Vuex也使用了单一状态树来管理应用层级的全部状态;
    • 单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护;

    注意:

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

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

    ​ 比如我们在vuex的state中存储了多个数据,在某个组件我们需要获取到这些数据,这时就会看到如下代码:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gSXVJTB8-1668407537869)(assets/image-20221107060923278.png)]

    我们发现$store.state 一直在重复写,那我们可以把这些设置为计算属性:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nqNUhTiF-1668407537870)(assets/image-20221107061214983.png)]

    但是我们还是发现计算属性中,也在重复写this.$store.state。vuex提供了mapState 辅助函数帮助我们生成计算属性.

     computed:mapState({
            
            //箭头函数可使代码更简练
            name: state => state.name,
            // 传字符串参数 'name' 等同于 `state => state.name`
            //给name定义一个别名
            xingming: 'name',
            //扩展函数,可以对name进行操作
            namePlusLocalState (state) {
                return this.hello + state.name;
            },
            sex:state=>state.sex,
            age:state=>state.age,
        }),
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组

     computed:mapState(["name","sex","age"]),
    
    • 1

    mapState 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。但是自从有了对象展开运算符,我们可以极大地简化写法:

    //对象展开运算符
    computed:{
        //局部计算属性
        localComputed () { /* ... */ },
        ...mapState(["name","sex","age"]),
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    5.3 Getter

    某些属性我们可能需要经过变化后来使用,这个时候可以使用getters:Vuex 允许我们在 store 中定义“getters”(可以认为是 store 的计算属性)

    上图我们中函数Getter 接受 state 作为其第一个参数:

     getters:{
            totalPrice(state){
                let total= 0;
                state.books.forEach((book)=>{
                    total += book.count*book.price;
                })
    
                return total;
            }
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    我们在其他组件可以访问Getter 对象,Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值:

     <div>
         <h2>总价:{{$store.getters.totalPrice}}h2>
    div>
    
    • 1
    • 2
    • 3

    getters可以接收第二个参数

     getters:{
            totalPrice(state,getters){
                let total= 0;
                state.books.forEach((book)=>{
                    total += book.count*book.price;
                })
    
                return total+","+getters.myName;
            },
            myName(state){
                return state.name;
            }
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

     getters:{
            totalPrice(state,getters){
                let total= 0;
                state.books.forEach((book)=>{
                    total += book.count*book.price;
                })
    
                return total+","+getters.myName;
            },
            myName(state){
                return state.name;
            },
            getBookById(state){
                return (id)=>{
                    return state.books.find(book=>
                        book.id === id
                    );
                }
            }
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    访问:

    <h2>书籍信息:{{$store.getters.getBookById(3)}}h2>
    
    • 1
    5.4 mapGetters 辅助函数

    mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

    <template>
        <div>
            <h2>总价:{{totalPrice}}h2>
            <h2>书籍信息:{{getBookById(3)}}h2>
        div>
    template>
    
    <script>
    import { mapGetters } from 'vuex'
    export default {
        name: 'Book',
        computed:{
            ...mapGetters(['totalPrice','getBookById']),
        },
    };
    script>
    
    <style lang="scss" scoped>
    
    style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    5.5 Mutation

    ​ 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:你可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qwD7qtlY-1668407537873)(assets/image-20221107115148803.png)]

    你不能直接调用一个 mutation 处理函数。这个选项更像是事件注册:“当触发一个类型为 INCREMENT 的 mutation 时,调用此函数。”要唤醒一个 mutation 处理函数,你需要以相应的 type 调用 store.commit 方法。

    在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:

     increment:function(context,value){
                context.commit("INCREMENT",{
                    value
                });
            },
    
    • 1
    • 2
    • 3
    • 4
    • 5

    INCREMENT(store,payload){
                store.sum +=payload.value;   
            },
    
    • 1
    • 2
    • 3

    提交 mutation 的另一种方式是直接使用包含 type 属性的对象:

    store.commit({
      type: 'increment',
      amount: 10
    })
    
    • 1
    • 2
    • 3
    • 4

    当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此处理函数保持不变:

    5.5.1 在组件中提交 Mutation

    ​ 你可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store

    <template>
        <div class="box">
            <h2>和:{{$store.state.sum}} --{{count}}h2>
            <input type="number" length="3" v-model="num"/>
            <input type="button" value="+" @click="add({value:num})"/>
            <input type="button" value="-" @click="sub({value:num})"/>
            <Counter/>
        div>
    template>
    
    <script>
    import {mapMutations } from 'vuex'
    export default {
        name: 'CalcSum',
    
        data() {
            return {
                num:1,
            };
        },
    
        mounted() {
            
        },
        methods: {
            /*
            add(){
                //通过dispatch去提交一个action
                this.$store.dispatch('increment', this.num);
                console.log(this.$store.state.sum)
                //强制刷新页面
                //this.$forceUpdate();
            },
            sub(){
                this.$store.dispatch('decrement', this.num);
                //this.$forceUpdate();
            },*/
            
            ...mapMutations({
                add:'INCREMENT',
                sub:'DECREMENT'
            }),
        },
        computed: {
            count () {
                 return this.$store.state.sum
            }
        }
    };
    script>
    
    <style lang="css" scoped>
        .box{
            width:70%;
            margin: auto;
        }
    style>
    
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    如果组件的事件函数名与Mutation中修改状态的函数名一样,我们可以简写:

    ...mapMutations(['INCREMENT','DECREMENT']),
    
    • 1

    mutation重要原则:

    ​ 一条重要的原则就是要记住 mutation 必须是同步函数,这是因为devtool工具会记录mutation的日记;每一条mutation被记录,devtools都需要捕捉到前一状态和后一状态的快照;但是在mutation中执行异步操作,就无法追踪到数据的变化;
    ​ 所以Vuex的重要原则中要求 mutation必须是同步函数;

    5.6 Action

    ​ 如果我们希望在Vuex中发送网络请求的话需要如何操作呢?那我们就使用Action

    Action 类似于 mutation,不同在于:

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

    让我们来注册一个简单的 action:

    const store = createStore({
      state: {
        count: 0
      },
      mutations: {
        increment (state) {
          state.count++
        }
      },
      actions: {
        increment (context) {
          context.commit('increment')
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 state 和 getters。当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了。

    5.6.1 actions的分发操作

    如何使用action呢?进行action的分发:

    • 分发使用的是 store 上的dispatch函数;

    • 同样的,它也可以携带我们的参数。

    • 也可以以对象的形式进行分发。

    乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作:

    5.6.2 actions的辅助函数

    action也有对应的辅助函数:使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store

    ttps://vuex.vuejs.org/zh/guide/modules.html) 时,你就知道 context 对象为什么不是 store 实例本身了。

    5.6.1 actions的分发操作

    如何使用action呢?进行action的分发:

    • 分发使用的是 store 上的dispatch函数;
      在这里插入图片描述

    • 同样的,它也可以携带我们的参数:
      [外链图片转存中...(img-Rx8YthXG-1668407537878)]

    • 也可以以对象的形式进行分发:
      [外链图片转存中...(img-fRw5Veuk-1668407537880)]

    乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作:

    5.6.2 actions的辅助函数

    action也有对应的辅助函数:使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store在这里插入图片描述

  • 相关阅读:
    【嵌入式开发学习】__如何将BootLoader与APP合并成一个固件
    C++语法——详细剖析类成员函数在内存中存储形式(包括静态)
    3ds MAX 2024版资源包下载分享 3ds Max三维建模软件资源包下载安装
    pytorch无法使用cuda
    委派构造函数
    RatSLAM配置(MATLAB版)
    《C++避坑神器·二十六》结构体报重定义错误问题和std::variant同时存储不同类型的值使用方式
    【MindSpore易点通】数据处理之Numpy数组的使用
    【力扣每日一题】1470. 重新排列数组
    java 替换list中值的方法分享
  • 原文地址:https://blog.csdn.net/H215919719/article/details/127846974