美文网首页Vue.jsVue.js专区vue
一个简单的vuex学习项目

一个简单的vuex学习项目

作者: 锋叔 | 来源:发表于2019-03-13 21:27 被阅读16次

    包含如何新建项目和上传github,包含vuex的State、Getters、Mutations、Actions、Module、表单处理。

    SimpleVuex点击下载

    • 注意事项,本人也是菜鸟一枚,下载后安装时把package-lock.json文件干掉再npm install
      image.png

    第一步,自己去注册一个GitHub的账号

    第二步,新建一个空项目

    第三步,本地找个盘儿,git clone 下来空项目

    第四步,下载vue-cli

    第五步,进入当前空项目路径,vue init webpack

    第六步,一系列yes or no ,配置完毕run起来

    第七步,把特么的HelloWorld组件改成你想要的样子

    第八步,退出来,安装vuex

    • npm install vuex --save
      PS 是--save 不是 --save-dev 具体区别请百度,我解释了你也不懂

    第九步,建立一个store文件夹和main.js同级

    • store具体结构如下
      └── store
      ├── index.js # 我们组装模块并导出 store 的地方
      ├── mutation-types.js #使用常量替代 mutation 事件类型,作用用的时候再告诉你
      └── modules
      ├── cart.js # 购物车模块
      └── products.js # 产品模块

    什么是Vuex?
    Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
    什么是状态管理模式?
    文档上絮絮叨叨,老子粗犷的总结一下,vuex就是建立一个全局数据库,每个数据都可修改和监控。
    项目为啥要用Vuex?
    传参的方法对于多层嵌套的组件将会非常繁琐,最难受的是对于非父子组件间的状态传递无能为力。Vuex只要是在这个项目里任何组件都能使用数据,操控数据状态。
    父子组件里经常特么的拷贝多份数据,且到处都是事件来修改这些数据,一旦功能繁琐,代码就是稀烂,阅读难交接难维护难上加难。Vuex不用你拷贝,任何组件都能修改数据状态,且是同一个数据状态,何乐不为?
    Vuex有什么缺点?
    一个难搞的概念和框架,会增加技术成本和项目周期,刷新数据状态变为初始化。

    第十步,我们先做一个最简单的例子

    • cart.js内容如下
    // 最简单的例子
    export default {
      // 定义状态
      state: {
        count: 0, // 货物数量
        str: null,
        arr: [],
        form: {}
      },
      getters: {
    
      },
      actions: {
    
      },
      mutations: {
        increment (state) { // 增一
          state.count++
        },
        reduce (state) { // 减一
          state.count--
        }
        
      }
    } 
    
    • index.js内容如下
    import Vue from 'vue'
    import Vuex from 'vuex'
    import cart from './modules/cart'
    
    Vue.use(Vuex) // 显式地通过 Vue.use() 来安装 Vuex
    
    export default new Vuex.Store({
      modules: {
        cart
      }
    })
    
    • main.js 把store注入全局,也就是vuex注入全局
    import Vue from 'vue'
    import App from './App'
    import router from './router'
    import store from './store' // 引入
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,
      store, // 注入全局
      components: { App },
      template: '<App/>'
    })
    

    第十一步,开始使用,新建相关组件

    • 和HelloWorld.vue组件同级新建cart.vue和product.vue
    • cart.vue
    <template>
      <div class="hello">
        <h1>我是购物车组件</h1>
        <button @click="increment">点击加1</button> <br>
        仔细观察产品数量:{{this.$store.state.cart.count}}
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
        }
      },
      created(){
        console.log(this.$store)
      },
      methods: {
        increment() {
          this.$store.commit('increment')
        }
      }
    }
    </script>
    
    • product.vue
    <template>
      <div class="hello">
        <h1>我是产品组件</h1>
        <button @click="reduce">点击减1</button> <br>
        仔细观察产品数量:{{this.$store.state.cart.count}}
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
        }
      },
      methods: {
        reduce() {
          this.$store.commit('reduce')
        }
      }
    }
    </script>
    
    • HelloWorld.vue
    <template>
      <div class="hello">
        <h1>{{ msg }}</h1>
        <h2>下面是两个组件</h2>
        <ul>
          <li>
            <cart></cart>
          </li>
          <li>
            <product></product>
          </li>
        </ul>
      </div>
    </template>
    
    <script>
    import cart from '../components/cart.vue'
    import product from '../components/product.vue'
    export default {
      name: 'HelloWorld',
      data() {
        return {
          msg: '欢迎来到我的vuex学习项目'
        }
      },
      components: {
        cart,
        product
      }
    }
    </script>
    
    <style scoped>
    h1,
    h2 {
      font-weight: normal;
    }
    ul {
      list-style-type: none;
      padding: 0;
    }
    li {
      display: inline-block;
      margin: 0 10px;
    }
    a {
      color: #42b983;
    }
    </style>
    

    ** 效果截图如下**


    image.png

    对于以上简单Vuex做个总结,this.$store.commit('reduce')触发store中cart.js的mutations中的increment (state) {state.count++},这样做的目的就是为了通过提交 mutation 的方式记录每次状态改变。其实你也可以直接this.$store.state.cart.count++,如果你经历过非父子组件间相互传值的痛苦,此时你应该是充满欣喜兴奋的,我们的两个组件就是毫无关系的组件但是却做到了数据同步。

    第十二步我们先把这个项目传到github去

    git add . // 这个点...看好了,有个点儿的哈。不会git的傻蛋老是说没得反应!!
    git commit -m "xxx" // 这是备注信息xxx
    git push origin master
    

    第十三步理解,State,Getters,Mutations,Actions

    一句话来概括他们的关系,state定义状态,getters监听状态,mutations更改状态但是不支持异步,actions触发mutations,但允许异步。

    State

    定义状态,我看了半天觉得我也没啥好讲的,就是定义和保存所有数据,了解下他的辅助函数mapState

    • store/index.js 添加根级state
    export default new Vuex.Store({
      state: {
        count: 0
      }
    })
    

    之所以是根级,是因为模块的state有一个声明空间,极其晦涩和难以理解,我们先易后难。

    • studyState.vue 和HelloWorld.vue同级存放
    <template>
      <div class="hello">
        <h1>State以及它的辅助函数mapState</h1>
        state.count:{{count}} <br>
        countAlias:{{countAlias}} <br>
        countPlusLocalState:{{countPlusLocalState}} <br>
      </div>
    </template>
    
    <script>
    import { mapState } from 'vuex'
    export default {
      computed: {
        ...mapState({
          count: state => state.count,
          // 传字符串参数 'count' 等同于 `state => state.count`
          countAlias: 'count',
          // 为了能够使用 `this` 获取局部状态,必须使用常规函数
          countPlusLocalState(state) {
            return state.study.count + 1
          }
        })
      }
    }
    </script>
    
    • HelloWorld.vue中做以下修改
      引入并注册studyState.vue和放到你觉得合适的地方使用。
     <ul>
        <li>
          <study-state></study-state>
        </li>
     </ul>
    
      import studyState from '../components/studyState.vue'
    
      components: {
        studyState
      }
    

    截图效果

    image.png

    mapState的第二种使用方式:
    当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。

     computed: mapState([
        'count', 'name'
      ])
    
    Getter
    • 可以认为是 store 的计算属性。getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。其实和state差不多!
      studyGetter.vue
    <template>
      <div>
        通过属性访问: <br>
        显示元素:{{this.$store.getters.showElement}} <br><br>
        可以接受其他 getter 作为第二个参数 <br>
        长度:{{showLength}} <br><br>
        通过id获取数组中符合的 <br>
        同id数组元素: {{this.$store.getters.getElementById(2)}} <br><br>
        mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性<br>
        元素名数组: {{elementNames}}<br><br>
        <!-- 如果你想将一个 getter 属性另取一个名字,使用对象形式 <br> -->
        <!-- {{names}} -->
    
      </div>
    </template>
    
    <script>
    import { mapGetters } from 'vuex'
    export default {
      computed: {
        showLength() {
          return this.$store.getters.showLength
        },
        ...mapGetters([
          'elementNames'
        ])
      },
      //  如果你想将一个 getter 属性另取一个名字,使用对象形式
      // computed: mapGetters({
      //   names: 'elementNames'
      // })
    }
    </script>
    
    • 在store/index.js中做以下修改
    export default new Vuex.Store({
      state: {
        count: 0,
        name: '默认名字',
        array: [{name: '元素A', id: 1, display: true}, {name: '元素B', id: 2, display: false}]
      },
      getters: {
        showElement: state => {
          return state.array.filter(ele => ele.display)
        },
        // getters可以作为第二参数
        showLength: (state, getters) => {
          return getters.showElement.length
        },
        // 通过方法获取,可以传参哦
        getElementById: (state) => (id) => {
          return state.array.find(ele => ele.id === id)
        },
        elementNames: state => {
          return state.array.map(ele => ele.name)
        }
      }
    
    • 和上面的步骤一样,你得在HelloWorld.vue中引入组件
    // 展示
    <ul>
      <li>
        <h1>Getters以及它的辅助函数mapGetters </h1>
      </li>
    </ul>
    <div style="padding-left: 300px;text-align: left;">
      <study-getters></study-getters>
    </div>
    // 引入
    import studyGetters from '../components/studyGetters.vue'
    // 注册
    components: {
       studyGetters
    }
    

    效果图

    image.png
    Mutations
    • 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
    mutations: {
        increment (state) {
          // 变更状态
          state.count++
        }
    }
    
    • store/index.js 修改如下
    export default new Vuex.Store({
      namespaced: true,
      state: {
        count: 0,
        name: '默认名字',
        array: [{
          name: '元素A',
          id: 1,
          display: true
        }, {
          name: '元素B',
          id: 2,
          display: false
        }],
        form: {
          name: null,
          age: null,
        }
      },
      getters: {
        showElement: state => {
          return state.array.filter(ele => ele.display)
        },
        // getters可以作为第二参数
        showLength: (state, getters) => {
          return getters.showElement.length
        },
        // 通过方法获取,可以传参哦
        getElementById: (state) => (id) => {
          return state.array.find(ele => ele.id === id)
        },
        elementNames: state => {
          return state.array.map(ele => ele.name)
        }
      },
      mutations: {
        increment(state) {
          // 变更状态
          state.count++
        },
        add(state, num) {
          state.count += num
        },
        addObj(state, obj) {
          state.count += Number(obj.num)
        },
        // 当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数
        addByType(state, payload) {
          state.count += payload.amount
        },
        // 使用常量
        [SET_NAME](state, form) {
          // state.form.name = form.name // 我们不建议使用这种方式
          Vue.set(state.form, 'name', form.name)
        },
        // 尝试异步
        [SET_AGE](state, form) {
          setTimeout(() => {
            Vue.set(state.form, 'age', form.age)
          }, 1000)
          setTimeout(() => {
            Vue.set(state.form, 'age', 12)
          }, 1100)
          setTimeout(() => {
            Vue.set(state.form, 'age', 34)
          }, 1200)
        }
      },
      modules: {
        cart,
        study,
        moduleA
      }
    })
    

    新建studyMutations.vue,内容如下

    <template>
      <div>
        通过Mutations触发状态更改: <br>
        state.count++ <button @click="increment()">加一</button> <br>
        state.count = {{this.$store.state.count}} <br>
        <p>
          我们不能直接调用this.$store.mutations('increment') <br>
          它更像是vue的methods,是注册了一个事件而已,调用它必须使用store.commit
        </p>
        Payload(提交载荷), 大白话就是可以传参 <br>
        <button @click="add(10)">点击加10</button> <br> <br>
        当你有多个参数时:传对象,实战中我们基本上都是传对象 <br>
        输入数字:<input type="number" v-model="form.num"><button @click="addObj(form)">增加</button> <br> <br>
        <br>
        提交 mutation 的另一种方式是直接使用包含 type 属性的对象: <br>
        <button @click="addByType(100)">增加100</button> <br> <br>
        调用常量方式定义的mutations修改姓名: <br>
        输入姓名:<input type="text" v-model="form.name"><button @click="setName(form)">提交</button> <br> <br>
        <br>
        state.form.name = {{this.$store.state.form.name}} <br>
        输入年龄:<input type="text" v-model="form.age"><button @click="setAge(form)">提交</button> <br> <br>
        尝试异步修改状态发现依旧是可以的,但是为啥官方文档说不支持异步呢?:<br>
        state.form.age = {{this.$store.state.form.age}} <br>
        <p>并不是不支持,而是无法追踪状态!这个演示我们需要用到devtool,异步vuex不搭理他!详情请看简书上我的截图演示!</p>
        <b>所以又了Actions</b>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          form: {
            num: 8,
            name: null
          }
        }
      },
      created() {
        console.log(this.$store)
      },
      computed: {
      },
      methods: {
        // 调用它必须使用store.commit
        increment() {
          this.$store.commit('increment')
        },
        // Payload(提交载荷), 大白话就是可以传参
        add(num) {
          // this.$store.commit('add', num)
        },
        // 传一个对象参数解决多参数问题
        addObj(obj) {
          this.$store.commit('addObj', obj)
        },
        addByType(num) {
          // 提交 mutation 的另一种方式是直接使用包含 type 属性的对象:
          this.$store.commit({
            type: 'addByType',
            amount: num
          })
        },
        // 调用常量方式定义的mutations修改姓名
        setName(form) {
          this.$store.commit('SET_NAME', form)
        },
        // 尝试异步
        setAge(form) {
          this.$store.commit('SET_AGE', form)
        }
      }
    }
    </script>
    
    • 然后注册和引用到HelloWorld组件里,截图如下


      image.png

      **mutations一样有辅助函数

    import { mapMutations } from 'vuex'
    
    export default {
      // ...
      methods: {
      /* 辅助函数
        ...mapMutations([
          'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
    
          // `mapMutations` 也支持载荷:
          'addObj' // 将 `this.addObj(obj)` 映射为 `this.$store.commit('addObj', obj)`
        ]),
        
        ...mapMutations({
          add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
        })
        */
    }
    
    解释下为啥mutations不支持异步
    • 安装vue-devtools 自行百度安装使用

    根据上图我输入不是三十四来分析

    mutations里的代码是

      mutations: {
        // 尝试异步
        [SET_AGE](state, form) {
          setTimeout(() => {
            Vue.set(state.form, 'age', form.age)
          }, 1000)
          setTimeout(() => {
            Vue.set(state.form, 'age', 12)
          }, 1100)
          setTimeout(() => {
            Vue.set(state.form, 'age', 34)
          }, 1200)
        }
      },
    

    ** 我们打开devtool工具


    image.png
    Actions
    • actions的存在就是为了触发mutations,只不过他支持了异步,所以我们虽然有时候觉得没啥子意义,但是约定俗成的会使用actions,这就是为了防止出现意外的bug。
    • 新建一个studyActions.vue 内容如下
      PS 如果有人和我一起在做的话,很多地方我考虑的不全面,估计会出问题,你可以查看我的github项目完整代码对比看看我是不是遗漏了某个步骤。
    <template>
      <div>
        输入身高:<input type="text" v-model="form.height"><button @click="setHeight(form)">提交</button> <br> <br><br>
        state.form.height = {{this.$store.state.form.height}} <br>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          form: {
            num: 8,
            name: null
          }
        }
      },
      methods: {
        // 尝试异步
        setHeight(form) {
          this.$store.dispatch('setHeight', form)
        }
      }
    }
    </script>
    
    • store目录下的index.js做以下修改
      state: {
        form: {
          height: null
        }
      }
      mutations: {
        /*** 这里是原来的代码我就不复制了**/
        // 尝试Actions异步
        [SET_HEIGHT](state, height) {
          Vue.set(state.form, 'height', height)
        },
      },
      actions: {
        setHeight (context, form) {
          // context Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
          context.commit('SET_HEIGHT', form.height)
          setTimeout(() => {
            context.commit('SET_HEIGHT', 180)
          }, 500)
          setTimeout(() => {
            context.commit('SET_HEIGHT', 185)
          }, 1000)
        }
      },
    

    mutations_types.js

    export const SET_HEIGHT = 'SET_HEIGHT' // 修改身高
    

    效果如下

    image.png
    PS 只有这样的结果才有意义!!使用vuex才有简直!!

    我们继续
    dispatch是一个分发actions的函数

        setHeight(form) {
          // 这儿和mutations的commit一样,actions需要用dispatch去触发
          this.$store.dispatch('setHeight', form)
        }
    

    最终版studyActions.vue

    <template>
      <div>
        输入身高:<input type="text" v-model="form.height"><button @click="setHeight(form)">提交</button> <br> <br>
        state.form.height = {{this.$store.state.form.height}} <br><br>
        <p>mapActions映射</p>
        <button @click="increment">提交increment, count++</button> <br>
        this.$store.state.count = {{this.$store.state.count}}<br> <br>
        <p>`mapActions` 也支持载荷:</p>
        输入姓名:<input type="text" v-model="form.name"><button @click="setName(form)">提交</button> <br>
        state.form.name = {{this.$store.state.form.name}} <br><br>
        <p>mapActions映射increment为add</p>
        <button @click="add">提交increment, count++</button> <br>
        <p>高级玩法</p>
        <button @click="actionA">加一后,再加一</button> <br>
        <p>进阶玩法</p>
        <button @click="actionB">加一后,再加十一</button> <br>
      </div>
    </template>
    
    <script>
    import { mapActions } from 'vuex'
    export default {
      data() {
        return {
          form: {
            num: 8,
            name: null
          }
        }
      },
      methods: {
        // 尝试异步
        setHeight(form) {
          // 这儿和mutations的commit一样,actions需要用dispatch去触发
          this.$store.dispatch('setHeight', form)
        },
        // mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store)
        ...mapActions([
          'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
    
          // `mapActions` 也支持载荷:
          'setName' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
        ]),
        ...mapActions({
          add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
        }),
        // 高级操作
        actionA() {
          this.$store.dispatch('actionA').then(() => {
            // 十秒以后再加一
            setTimeout(() => {
              this.add()
            }, 1000)
          })
        },
        // 进阶级
        actionB() {
          this.$store.dispatch('actionB')
        }
      }
    
    }
    </script>
    

    store下的index.js

    
      actions: {
        setHeight (context, form) {
          // context 你就理解成actions可以传入一个参数就是store本身(虽然并不是本身)。包含里面的state,getter,mutation,action
          context.commit('SET_HEIGHT', form.height)
          setTimeout(() => {
            context.commit('SET_HEIGHT', 180)
          }, 500)
          setTimeout(() => {
            context.commit('SET_HEIGHT', 185)
          }, 1000)
        },
        // actions触发mutations的increment
        increment({commit, state}) {
          commit('increment', state)
        },
        // mutation中的SET_NAME
        setName({commit, state}, form) {
          commit('SET_NAME', form) 
        },
        // 组合 Action
        actionA ({ commit }) {
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              commit('increment')
              resolve()
            }, 1000)
          })
        },
        // 你甚至还能这么玩
        actionB ({ dispatch, commit }) {
          return dispatch('actionA').then(() => {
            setTimeout(() => {
              commit('add', 10)
            }, 1000)
          })
        }
      },
    

    这里面讲解下几个难点

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

    高阶玩法你需要明白 store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise。

    整篇文章写得很碎,项目会不停的更新,所以有可能和简书上的不一致,请自行理解,这算是初稿!后面还有模块的讲解和关于表单处理,下一篇吧。

    相关文章

      网友评论

        本文标题:一个简单的vuex学习项目

        本文链接:https://www.haomeiwen.com/subject/aeqnpqtx.html