zoukankan      html  css  js  c++  java
  • vuex 学习--由浅入深

    1.学习指南

    1. 官网学习--掌握思想和 API 的使用
    2. 实战--具体使用,巩固所学
    3. 较复杂项目--真正应用于实践中
    4. 源码学习

    在这几个过程中都可以在网上查找优秀的博客文章辅助理解学习。

    最后可以通过 5W1H 来检验自己对技术的掌握程度。

    2.面试官问:你使用过 Vuex 吗?

    对于 vuex 相关的面试题,我们可以从vuex 是什么?为什么要使用 vuex ? 适用于哪些场景?如何使用 vuex ? 它的实现原理是什么样的?这几个方面准备。以下是简洁描述,具体的需要参照 vuex 的官网进行学习。

    2.1 vuex 是什么?

    根据官网上的描述,vuex是一个专为vue.js应用程序开发的状态管理模式。
    它采用集中式存储/管理 应用的所有组件的状态,并以相应的规则保证 状态以一种可预测的方式发生变化。

    2.2 为什么使用 vuex?

    当应用遇到多个组件共享状态的时候,可能会出现,多个视图依赖于同一状态,来自不用视图的行为需要变更同一状态;像这种比较复杂的数据传递问题,就可以使用 vuex 帮助我们进行开发。

    2.3 如何使用 vuex?

    • vuex 把组件的共享状态抽取出来,放到store(仓库)里边;它的状态存储是响应式的。
    • 当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
    • 不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样我们可以方便地跟踪每一个状态的变化。

    vuex有哪几种属性?

    有五种,分别是 State、 Getter、Mutation 、Action、 Module

    • state---state为单一状态树,在state中需要定义我们所需要管理的数组、对象、字符串等等,只有在这里定义了,在vue.js的组件中才能获取你定义的这个对象的状态。
    • getter---getter有点类似vue.js的计算属性,当我们需要从store的state中派生出一些状态,那么我们就需要使用getter,getter会接收state作为第一个参数,而且getter的返回值会根据它的依赖被缓存起来,只有getter中的依赖值(state中的某个需要派生状态的值)发生改变的时候才会被重新计算。
    • mutation---更改store中state状态的唯一方法就是提交mutation,就很类似事件。每个mutation都有一个字符串类型的事件类型和一个回调函数,我们需要改变state的值就要在回调函数中改变。我们要执行这个回调函数,那么我们需要执行一个相应的调用方法:store.commit。
    • action---action可以提交mutation,在action中可以执行store.commit,而且action中可以有任何的异步操作。在页面中如果我们要用这个action,则需要执行store.dispatch
    • module---module其实只是解决了当state中很复杂臃肿的时候,module可以将store分割成模块,每个模块中拥有自己的state、mutation、action和getter。

    vuex的数据传递流程:

    vuex

    mutations 里面的每个函数都会有一个 state 参数,这样就可以在 mutations 里面进行 state 的数据修改,当数据修改完毕后,会传导给页面。页面的数据也会发生改变。

    当组件进行数据修改的时候我们需要调用 dispatch 来触发 actions 里面的方法。actions 里面的每个方法中都会有一个 commit 方法,当方法执行的时候会通过 commit 来触发 mutations 里面的方法进行数据的修改。

    3.简单项目

    通过 vuex 基础知识的学习,根据官网的示例,我们可以进行实战训练。先来做一个简单的 demo。实现的效果很简单,直接看图。

    vuex-0

    首先创建一个vue项目

    vue init webpack projectName

    安装vuex

    npm install vuex --save

    在 src 下创建一个 store 目录, 新建以下文件:

    • index.js
    • state.js
    • getters.js
    • mutations.js
    • actions.js

    在 main.js 中引入和使用

    import Vue from 'vue'
    import App from './App'
    import router from './router'
    import Vuex from "vuex"
    import store from "./store"
    
    Vue.config.productionTip = false
    
    Vue.use(Vuex)
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,
      store,
      components: { App },
      template: '<App/>'
    })
    

    在 index.js 中

    import Vue from 'vue'
    import Vuex from 'vuex'
    import state from './state'
    import getters from './getters'
    import mutations from './mutations'
    import actions from './actions'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state,
      getters,
      mutations,
      actions
    })
    

    首先实现第一个,count1

    在state.js中

    export default {
      count1: 0
    }
    

    在 mutations.js 中

    export default {
      countInc(state, v) {
        state.count1 = state.count1 + v
      }
    }
    

    在 App.vue 中

    <template>
      <div id="app">
        <h1>count1: {{count1}}</h1>
        <button @click="countIncrease">点我+100</button>
      </div>
    </template>
    
    <script>
      export default {
        name: 'App',
        computed: {
          count1() {
            return this.$store.state.count1;
          }
        },
        methods: {
          countIncrease() {
            this.$store.commit("countInc", 100);
          }
    	}
      }
    </script>
    
    <style>
    
    </style>
    
    

    使用 mapState, mapMutations 辅助函数简化代码

    export default {
      name: 'App',
      computed: {
        /* count1() {
          return this.$store.state.count1;
        } */
        // 将 this.count1 为 this.$store.state.count1
        ...mapState(['count1'])
      },
      methods: {
        /* countIncrease() {
          this.$store.commit("countInc", 100);
        }, */
        // 将 `this.countInc(100)` 映射为 `this.$store.commit('countInc', 100)`
        ...mapMutations(['countInc']),
        countIncrease() {
          this.countInc(100);
        },
      }
    }
    

    实现 count2 和 count3

    在state.js中增加

    export default {
      count1: 0,
      count2: 0,
      count3: 0
    }
    

    在 mutations.js 中增加

    export default {
      countInc(state, v) {
        state.count1 = state.count1 + v
      },
      increment(state) {
        state.count2++
      },
      incrementAsync(state) {
        state.count3++
      }
    }
    

    在 actions 中

    export default {
      /* increment(context) {
        context.commit('increment')
      } */
      increment({
        commit
      }) {
        commit('increment')
      },
      incrementAsync({
        commit
      }) {
        setTimeout(() => {
          commit('incrementAsync')
        }, 1000)
      }
    }
    

    在 App.vue 中增加

    <template>
      <div id="app">
        <h1>count1: {{count1}}</h1>
        <button @click="countIncrease">点我+100</button>
        <h1>count2: {{count2}}</h1>
        <button @click="incrementFn">点我+1</button>
        <h1>count3: {{count3}}</h1>
        <button @click="incrementAsyncFn">点我延时1秒+1</button>
        <h3>花名册</h3>
        <ul class="ul">
          <li v-for="item in people">姓名:{{item.name}}---年龄:{{item.age}}</li>
        </ul>
        <h3>年满十八</h3>
        <ul class="ul">
          <li v-for="item in adults">姓名:{{item.name}}---年龄:{{item.age}}</li>
        </ul>
      </div>
    </template>
    
    <script>
      import {
        mapState,
        mapMutations,
        mapActions
      } from 'vuex'
      export default {
        name: 'App',
        components: {},
        computed: {
          // 将 this.count 为 this.$store.state.count
          ...mapState([
            'count1',
            'count2',
            'count3'
          ])
        },
        methods: {
          // 将 `this.countInc(100)` 映射为 `this.$store.commit('countInc', 100)`
          ...mapMutations(['countInc']),
          countIncrease() {
            this.countInc(100);
          },
    
          // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
          ...mapActions(['increment', 'incrementAsync']),
          incrementFn() {
            this.increment()
          },
          incrementAsyncFn() {
            this.incrementAsync()
          }
        }
      }
    </script>
    
    <style>
    
    </style>
    

    getter用法示例

    在state.js中增加

    export default {
      count1: 0,
      count2: 0,
      count3: 0,
      people: [{
          name: '小易',
          age: 20
        },
        {
          name: '小烊',
          age: 18
        },
        {
          name: '小千',
          age: 36
        },
        {
          name: '小玺',
          age: 27
        },
        {
          name: '小江',
          age: 24
        },
        {
          name: '小六',
          age: 8
        },
        {
          name: '小七',
          age: 6
        }
      ]
    }
    

    在 getters.js 中

    export default {
      adults: (state) => {
        return state.people.filter(item => {
          if (item.age >= 18) {
            return true
          }
        })
      }
    }
    

    在 App.vue 中增加

    <template>
      <div id="app">
        <h1>count1: {{count1}}</h1>
        <button @click="countIncrease">点我+100</button>
        <h1>count2: {{count2}}</h1>
        <button @click="incrementFn">点我+1</button>
        <h1>count3: {{count3}}</h1>
        <button @click="incrementAsyncFn">点我延时1秒+1</button>
        <h3>花名册</h3>
        <ul class="ul">
          <li v-for="item in people">姓名:{{item.name}}---年龄:{{item.age}}</li>
        </ul>
        <h3>年满十八</h3>
        <ul class="ul">
          <li v-for="item in adults">姓名:{{item.name}}---年龄:{{item.age}}</li>
        </ul>
      </div>
    </template>
    
    <script>
      import {
        mapState,
        mapGetters,
        mapMutations,
        mapActions
      } from 'vuex';
      export default {
        name: 'App',
        components: {},
        computed: {
          // 将 this.count 为 this.$store.state.count
          ...mapState([
            'count1',
            'count2',
            'count3',
            'people'
          ]),
          // 使用对象展开运算符将 getter 混入 computed 对象中
          ...mapGetters(['adults'])
        },
        methods: {
          // 将 `this.countInc(100)` 映射为 `this.$store.commit('countInc', 100)`
          ...mapMutations(['countInc']),
          countIncrease() {
            this.countInc(100);
          },
    
          // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
          ...mapActions(['increment', 'incrementAsync']),
          incrementFn() {
            this.increment()
          },
          incrementAsyncFn() {
            this.incrementAsync()
          }
        }
      }
    </script>
    
    <style>
    
    </style>
    

    4.课程学习项目

    4.1 项目简介

    项目来源
    vuex基础入门

    目标

    • 制作一个需要账号登录的课程学习项目
    • 不同的课程需要不同的会员等级,实现购买会员功能,课程分享

    功能

    • 通过 state.userInfo 控制用户登录路由限制
    • 多组件共享 state.userStatus 和 state.vipLevel 状态
    • 多组件修改 state.userStatus 和 state.vipLevel

    先看一下实现的效果
    vuex-1

    首先创建一个vue项目

    vue init webpack projectName

    安装 vuex vue-router less less-loader

    npm install vuex --save

    npm install vue vue-router --save

    npm install less less-loader --save

    在 src 下创建一个 store 目录, 新建以下文件:

    • index.js
    • state.js
    • getters.js
    • mutations.js
    • actions.js

    在 main.js 中引入和使用,同时创建全局的导航守卫

    import Vue from "vue"
    import App from "./App.vue"
    import Vuex from "vuex"
    import router from "./router"
    import store from "./store"
    
    Vue.config.productionTip = false
    
    Vue.use(Vuex)
    
    // 创建全局的导航守卫
    router.beforeEach((to, from, next) => {
        if (store.state.userInfo || to.path === "/login") {
            next()
        } else {
            next({
                path: "/login"
            })
        }
    })
    
    new Vue({
      el: '#app',
      router,
      store,
      components: { App },
      template: '<App/>'
    })
    

    在 index.js 中

    import Vue from 'vue'
    import Vuex from 'vuex'
    import state from './state'
    import getters from './getters'
    import mutations from './mutations'
    import actions from './actions'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state,
      getters,
      mutations,
      actions
    })
    

    router.js

    import Vue from "vue"
    import VueRouter from "vue-router"
    
    Vue.use(VueRouter)
    
    const router = new VueRouter({
        mode: "history",
        base: "/",
        routes: [
            {
                path: "/login",
                name: "login",
                component: () => import("./pages/login.vue")
            },
            {
                path: "/",
                name: "index",
                component: () => import("./pages/index.vue")
            },
            {
                path: "/userCenter",
                name: "userCenter",
                component: () => import("./pages/userCenter.vue")
            },
            {
                path: "/course/:id",
                name: "course",
                component: () => import("./pages/course.vue")
            }
        ]
    })
    
    export default router
    

    在 state.js 中

    export default {
        userInfo: "",
        userStatus: "", 
        vipLevel: ""
    }
    

    userStatus 三种取值情况:0 -> 普通;1 -> vip;2-> 高级vip

    getters.js

    export default {
        memberInfo(state) {
            switch (state.userStatus) {
                case 0:
                    return "普通会员"
                case 1:
                    return "vip会员"
                case 2:
                    return `高级V${state.vipLevel}会员`
                default:
                    return "普通会员"
            }
        }
    }
    

    mutations.js

    export default {
        login(state, v) {
            state.userInfo = v
        },
        // 创建用户信息
        setMemberInfo(state, v) {
            state.userStatus = v.userStatus
            state.vipLevel = v.vipLevel
        }
    }
    

    actions.js

    import { resolve } from "any-promise"
    
    export default {
        // 第二个参数表示要修改的数据
        buyVip({ commit }, e) {
          // 这里简单模拟一下异步操作, 模拟与后端的交互
            return new Promise((resolve, reject) => {
                // mock api 交互
                setTimeout(() => {
                    // 修改本地state
                    commit("setMemberInfo", {
                        userStatus: e.userStatus,
                        vipLevel: e.vipLevel
                    })
                    resolve("购买成功")
                }, 1000)
            })
        },
        getFreeVip({ commit, state }) {
            // mock api 交互
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    if (state.userStatus === 0) {
                        commit("setMemberInfo", {
                            userStatus: 1,
                            vipLevel: 0
                        })
                        resolve("分享成功,您已获得一个月的高级vip会员")
                    } else {
                        resolve("分享成功")
                    }
                }, 1000)
            })
        }
    }
    

    4.2 登录页面 login.vue

    login

    关键代码

    <template>
      <div class="login">
      
        <button class="btn" @click="login">登录</button>
    
      </div>
    </template>
    
    <script>
      export default {
        data() {
          return {
            isHidden: false,
            isPassword: true,
            logs: [],
            form: {
              account: "",
              password: ""
            }
          };
        },
        methods: {
          login() {
            if (!this.form.account && !this.form.password) {
              alert("请填写账号密码");
              return false;
            }
            const that = this;
            setTimeout(() => {
              this.$store.commit("login", {
                account: that.form.account,
                password: that.form.password
              });
              // 假设拿到用户的数据,存到 store 里
              this.$store.commit("setMemberInfo", {
                userStatus: 0,
                vipLevel: 0
              });
              // 提交完成后,跳转到首页?
              that.$router.push("./");
            }, 500);
          }
        }
      };
    </script>
    

    与 mutations.js 结合看

    export default {
        login(state, v) {
            state.userInfo = v
        },
        // 创建用户信息
        setMemberInfo(state, v) {
            state.userStatus = v.userStatus
            state.vipLevel = v.vipLevel
        }
    }
    

    4.3 首页 index.vue

    登录成功后,进入首页 index.vue

    index

    <template>
        <div class="index">
            <h1>你好</h1>
            <p class="text">
                尊敬的
                <!-- <span style="color: red;">{{this.$store.getters.memberInfo}}用户</span>,欢迎来到德莱联盟! -->
                <span style="color: red;">{{memberInfo}}用户</span>,欢迎来到前端学堂!
            </p>
    
            <button class="footer-opt btn" @click="recharge">充值</button>
        </div>
    </template>
    
    <script>
    import card from "../components/card";
    import { mapGetters, mapState } from "vuex";
    export default {
        components: {
            card
        },
        data() {
            return {
                courseList: []
            };
        },
        computed: {
            ...mapState(["userStatus", "vipLevel"]),
            ...mapGetters(["memberInfo"])
        },
        mounted() {},
        methods: {
            recharge() {
                this.$router.push("./userCenter");
            },
            goVideoList(e) {
                const res = this.checkPermission(e);
                if (res) {
                    this.$router.push({
                        name: "course",
                        params: {
                            id: e.id
                        }
                    });
                } else {
                    alert("权限不足,无法观看");
                }
            },
            checkPermission(e) {
                if (this.userStatus >= e.userStatus) {
                    if (this.vipLevel >= e.vipLevel) {
                        return true;
                    } else {
                        return false;
                    }
                } else {
                    return false;
                }
            }
        }
    };
    </script>
    

    memberInfo 来自于

    export default {
        memberInfo(state) {
            switch (state.userStatus) {
                case 0:
                    return "普通会员"
                case 1:
                    return "vip会员"
                case 2:
                    return `高级V${state.vipLevel}会员`
                default:
                    return "普通会员"
            }
        }
    }
    

    4.4 用户中心 userCenter.vue

    user

    点击充值,进入用户中心 userCenter.vue

    <template>
    	<button class="item-content__btn" @click="buy(item)">购买</button>
    </template>
    
    <script>
    import { mapState, mapGetters } from "vuex";
    export default {
        data() {
            return {
                vipList: []
            };
        },
        computed: {
            ...mapState(["userInfo"]),
            ...mapGetters(["memberInfo"])
        },
        methods: {
            buy(e) {
                this.$store.dispatch("buyVip", e).then(res => {
                    alert(res);
                });
            }
        }
    };
    </script>
    
    

    触发 actions.js 中的

    import { resolve } from "any-promise"
    
    export default {
        // 第二个参数表示要修改的数据
        buyVip({ commit }, e) {
          // 这里简单模拟一下异步操作, 模拟与后端的交互
            return new Promise((resolve, reject) => {
                // mock api 交互
                setTimeout(() => {
                    // 修改本地state
                    commit("setMemberInfo", {
                        userStatus: e.userStatus,
                        vipLevel: e.vipLevel
                    })
                    resolve("购买成功")
                }, 1000)
            })
        }
    }
    

    4.5 课程页面 course.vue

    course

    <template>
        <div class="course">
            <button class="share-btn" @click="share">分享</button>
        </div>
    </template>
    
    <script>
    import { mapState, mapGetters } from "vuex";
    
    export default {
        data() {
            return {};
        },
        methods: {
            share() {
                // 简单分享
                let c = confirm("课程分享,地址: http://www.muke.com");
                if (c) {
                    // 修改state userStatus
                    this.$store.dispatch("getFreeVip").then(res => {
                        alert(res);
                    });
                } else {
                    console.log("取消");
                }
            }
        }
    };
    </script>
    

    这个页面要关注的是分享的功能,普通的会员用户点击分享按钮,确定后完成分享,即可获得免费的一个月的 VIP 权限,给它绑定一个 click 事件.

    vuex-2

    触发 actions.js 中的 getFreeVip

    import { resolve } from "any-promise"
    
    export default {
        getFreeVip({ commit, state }) {
            // mock api 交互
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    if (state.userStatus === 0) {
                        commit("setMemberInfo", {
                            userStatus: 1,
                            vipLevel: 0
                        })
                        resolve("分享成功,您已获得一个月的高级vip会员")
                    } else {
                        resolve("分享成功")
                    }
                }, 1000)
            })
        }
    }
    

    完整的代码可以在我的仓库里查看。vuex-demo

    5.项目中使用

    可以参考我写的另一篇文章,Vue.js开发去哪儿网WebApp。
    2.4 使用vuex实现数据共享

    6.继续学习

    • 源码的学习--可以参考黄轶老师的《vue.js揭秘》
    • 掘金文章--Vuex面试题汇总
  • 相关阅读:
    爬取校园新闻首页的新闻
    网络爬虫基础练习
    【mongoDB实战】mongo集群---主从复制篇
    【mongoDB实战】聚合管道--$unwind
    【mongoDB实战】聚合管道--$unwind
    【mongoDB实战】mongoDB数据导入和导出
    【mongoDB实战】mongoDB数据导入和导出
    【mongoDB实战】mongoDB数据备份和还原
    【mongoDB实战】mongoDB数据备份和还原
    【Restful】三分钟彻底了解Restful最佳实践
  • 原文地址:https://www.cnblogs.com/chrislinlin/p/12577813.html
Copyright © 2011-2022 走看看