一个简单的vuex学习项目

包含如何新建项目和上传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。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容