前言

首先说明这并不是一个教程贴,而记事本应用是网上早有的案例,对于学习 vuex 非常有帮助。我的目的是探索 vuex 2.0 ,然后使用 vuejs 2.0 + vuex 2.0 重写这个应用,其中最大的问题是使用 vue-cli 构建应用时遇到的问题。通过这些问题深入探索 vue 以及 vuex

我对于框架的学习一直断断续续,最先接触的是 react,所以有一些先入为主的观念,喜欢 react 更多一点,尤其在应用的构建层面来说。之所以断断续续,是因为自己 JS 基础较弱,刚开始学习的时候,只是比着葫芦画瓢,虽然可以做出点东西,但对于其中的一些概念仍然云里雾里,不知所云,无法深入理解框架。所以我又临时放弃框架的学习,开始学习 JS 基础。事实证明打牢基础之后,学习框架以及理解框架是神速的。而学习 webgl 和 three.js 的过程与此类似。没有 webgl 的基础,学习 three.js 只会停留在初级阶段。

我在过去的半年参加了很多面试,几乎无一例外的都会被问框架的使用情况,但是其中很多公司属于随波逐流,使用框架比较盲目。甚至觉得使用框架是极其高大上的事情。虽然我学习过框架,但毕竟没有深入学习也没有拿得出手的项目,所以只是只言片语的说两句,大部分知识是懵懂的。然而面对面试官不屑的神情以及以此作为选拔的指标,心想这样的面试官太肤浅。当然很多公司的面试还是以基础为主。框架属于探索,互相学习的状态。我在这篇文章中强调一点,学习能力以及解决问题的能力更重要。

开始吧

言归正传,对于这个笔记本案例,大家可以直接百度搜 vue notes ,这是一篇英文教程,大家看到的都是翻译的。在刚开始 vue 资料稀缺的时候,这样的文章非常珍贵。demo 点这里。说白了,算是 todoMVC 案例的一个变体。当初觉得这个例子非常好,想跟着学一学,结果一拖半年过去了。这几天终于抽时间把这个例子敲了一遍。学习在于举一反三。如果大家按照网上教程来做,那么 NPM 包默认安装的都是最新版本,运行会报错。所以如果用 vuex 2 要怎么写呢?

以下是 notes-vuex-app 的源文件目录:

在使用 vue 2 重写这个 app 之前,我在想能不能不改变文件目录结构以及配置位置呢?就是用比较生硬的方式重写,或者说单纯的语法修改。事实是可行的,否则我就不会写这篇文章了。然而面对的问题非常多,但却因此深入的理解了 vue 以及 vuex。最大的问题是 webpack 的构建,如果使用 webpack 2.0+的话,坑比较多。本人是菜鸟,所以最终选择了 vue-cli 提供的两个 webpack 的模板,分别是 webpack-simplewebpack,我先使用 webpack-simple,它和原 app 的结构基本吻合。目录如下:

使用 vue-cli 生成基本目录之后,再安装 vuex2 。

main.js 的小改动

原示例 main.js 如下所示,但运行出错了,主要是 Vue 2 的根实例渲染稍有变化

  1. import Vue from 'vue'
  2. import store from './vuex/store'
  3. import App from './components/App.vue'
  4.  
  5. new Vue({
  6. store, // 注入到所有子组件
  7. el: 'body',
  8. components: { App }
  9. })

改正之后:

  1. import Vue from 'vue'
  2. import store from './vuex/store'
  3. import App from './components/App.vue'
  4.  
  5. new Vue({
  6. store, // inject store to all children
  7. el: '#app',
  8. template: '<App/>',
  9. components: { App }
  10. })

或者

  1. import Vue from 'vue'
  2. import store from './vuex/store'
  3. import App from './components/App.vue'
  4.  
  5. new Vue({
  6. store, // inject store to all children
  7. el: '#app',
  8. render: h => h(App)
  9. })

vuex 2 的变化

这个应用改写的主要问题集中在 vuex 2 的变化上,这些变化确实会让人感到凌乱,我无数次抓耳挠腮的骂娘。不过通过官方给出的示例也可以看出一些端倪。

首先是 action.js,只需注意一点,所有的 dispatch 都要改成 commit

  1. export const addNote = ({ commit }) => {
  2. commit('ADD_NOTE')
  3. }
  4.  
  5. export const editNote = ({ commit }, e) => {
  6. commit('EDIT_NOTE', e.target.value)
  7. }
  8.  
  9. export const deleteNote = ({ commit }) => {
  10. commit('DELETE_NOTE')
  11. }
  12.  
  13. export const updateActiveNote = ({ commit }, note) => {
  14. commit('SET_ACTIVE_NOTE', note)
  15. }
  16.  
  17. export const toggleFavorite = ({ commit }) => {
  18. commit('TOGGLE_FAVORITE')
  19. }

store.js 变化也不大,但是要注意几个地方:

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. import * as actions from './actions'
  4.  
  5. Vue.use(Vuex)
  6.  
  7. const state = {
  8. notes: [],
  9. activeNote: {}
  10. }
  11.  
  12. const mutations = {
  13. ADD_NOTE (state) {
  14. const newNote = {
  15. text: 'New note',
  16. favorite: false
  17. }
  18. state.notes.push(newNote)
  19. state.activeNote = newNote
  20. },
  21.  
  22. EDIT_NOTE (state, text) {
  23. state.activeNote.text = text
  24. },
  25.  
  26. DELETE_NOTE (state) {
  27. state.notes.splice(state.notes.indexOf(state.activeNote),1)
  28. state.activeNote = state.notes[0] || {}
  29. },
  30.  
  31. TOGGLE_FAVORITE (state) {
  32. state.activeNote.favorite = !state.activeNote.favorite
  33. },
  34.  
  35. SET_ACTIVE_NOTE (state, note) {
  36. state.activeNote = note
  37. }
  38. }
  39.  
  40. const getters = {
  41. notes: state => state.notes,
  42. activeNote: state => state.activeNote,
  43. activeNoteText: state => state.activeNote.text
  44. }
  45.  
  46. export default new Vuex.Store({
  47. state,
  48. mutations,
  49. actions,
  50. getters
  51. })

原示例文件中没有将 getters 写到 store.js 中,而是直接在组件中定义的。为了更清晰,我仿照官方示例也提取出来写在了 store.js 中,这样在组件中调用时比较方便。其次也引入了 action.js,并作为 actions 对象传递给 Vuex.store(),这算是 vuex 的标准写法吧,对于后面在组件中调用比较有利。

其中要注意 DELETE_NOTE (state){} 这个方法,原示例使用了 vue1 提供的 remove 方法,但是 vue2 中去掉了这个方法。仔细想想就会明白,这个函数的作用就是删除 notes 数组中的元素。可以使用原生的 splice 方法。如果 JS 基础扎实的话,这里应该很好理解,没有什么大问题。其次相比原示例,添加一个删除后操作的判断。

我之前一直不太理解 flux 的概念,感觉像是新东西,完全不知道它的目的及作用。换成 Vuex,还是有点稀里糊涂。但是通过修改这个示例,基本算是开窍了。这些东西本身并没有玄机奥妙,想一想,如果我们不用框架,而是自己手写一个 todoMVC 时要怎么做?应该也是这样的思路,定义一个 notes 数组变量以及 activeNote 的变量。然后在创建一些改变状态的方法。我在面试中遇到过一个情况,面试官反复问我为什么需要使用框架,用 jQuery 不是也可以实现吗?这样说确实没错,用比较原始的方法当然可以做,只是代码结构会冗余或者凌乱,缺少小而美的特点。框架以及设计模式对代码做了整合封装,对于一个 CURD 应用比较友好,实现起来更方便更简单。我对于 Vuex 的理解就是,它是一个对象,封装了与状态相关的方法和属性。而所谓的状态就是点击、按键等操作之后的变化。

组件中使用 vuex

先看一下 Toolbar.vue 这个组件。修改后的代码如下:

  1. <template>
  2. <div id="toolbar">
  3. <i @click="addNote" class="glyphicon glyphicon-plus"></i>
  4. <i @click="toggleFavorite"
  5. class="glyphicon glyphicon-star"
  6. :class="{starred: activeNote.favorite}"></i>
  7. <i @click="deleteNote" class="glyphicon glyphicon-remove"></i>
  8. </div>
  9. </template>
  10.  
  11. <script>
  12.  
  13. import { mapGetters, mapActions } from 'vuex'
  14.  
  15. export default {
  16. computed: mapGetters([
  17. 'activeNote'
  18. ]),
  19. methods: {
  20. ...mapActions([
  21. 'addNote',
  22. 'deleteNote',
  23. 'toggleFavorite'
  24. ])
  25. }
  26. }
  27. </script>

通过和原示例代码对比,这里的区别一目了然。我通过在控制台打印 Vue 实例,折腾很长时间才大体明白怎么回事。vuex 1 在组件中使用时会直接将 getters 以及 actions 挂到 vuex 这个属性上,没有提供 mapGettersmapActions 等一些方法。而 vuex2 使用 mapGettersmapActions 等一些方法将 actions 的方法挂到 Vue 实例上。总的来说,都是把 actions 的方法挂到 Vue 实例上。我们从这个层面上谈谈 Vue 实例,Vue 2 的变化就是其属性的变化。比如 Vue1 中在 methods 中添加的方法可以在 vue 实例的 $options 属性中查看,而 vue2 中这些方法可以直接在第一级属性中查找或者在 $options 属性下的原型方法中 __proto__ 寻找。在 vue1 中可以查看 vuex 这个属性,但是 vue2 中移除了。至于其它的不同,大家可以自己对比,通过这种方式,可以深入理解 vue 的设计思想。

下图是 Vue1 实例截图:

ES5 实现扩展运算符

假设其它组件都以这种方式改好了,就在我们满心欢喜地运行示例时,又报错了。问题出在扩展运算符 ... 上,webpack-simple 这个模板无法解析 ES6 的 ...。为此,我又折腾了很久,想试着修改 webpack  的配置文件,但改动太大。我妥协了,决定抛弃扩展运算符,手写这个方法。当然如果使用 webpack 的模板就没有问题,这个比较简单,我们最后再说。

手写扩展运算符 ... 之前,我们先看一下 mapActions 这个方法。对于 mapGetters 以及 mapActions 这两个函数,最简单的理解办法就是查看 vuex 的源码,最终返回的是一个对象。也就是根据需要获取 store.jsactions 对象的某些方法。然后通过扩展运算符把返回的对象拆开然后挂到 Vue 实例上。举例来说(以下只是扩展运算符的用法之一,别的用法可以参考其它的文章):

  1. var obj = {
  2. a: 1,
  3. b: 2,
  4. }
  5.  
  6. var methods = {
  7. ...obj
  8. }
  9.  
  10. // console.log(methods)
  11. {
  12. a: 1,
  13. b: 2
  14. }

明白扩展运算符的用法之后就好办了。为了简单一点,我直接使用 babel 官网的在线解析器,查看扩展运算符的 ES5 写法。

  1. // ES5 实现扩展运算符...
  2. var _extends = Object.assign || function(target) {
  3. for (var i = 1; i < arguments.length; i++) {
  4. var source = arguments[i];
  5. for (var key in source) {
  6. if (Object.prototype.hasOwnProperty.call(source, key)) {
  7. target[key] = source[key];
  8. }
  9. }
  10. }
  11. return target;
  12. };

完整的 Toolbar.vue 组件代码如下:

  1. <template>
  2. <div id="toolbar">
  3. <i @click="addNote" class="glyphicon glyphicon-plus"></i>
  4. <i @click="toggleFavorite"
  5. class="glyphicon glyphicon-star"
  6. :class="{starred: activeNote.favorite}"></i>
  7. <i @click="deleteNote" class="glyphicon glyphicon-remove"></i>
  8. <i @click="_test" class="glyphicon glyphicon-remove"></i>
  9. </div>
  10. </template>
  11.  
  12. <script>
  13.  
  14. import { mapGetters, mapActions } from 'vuex'
  15.  
  16. // ES5 实现扩展运算符...
  17. var _extends = Object.assign || function(target) {
  18. for (var i = 1; i < arguments.length; i++) {
  19. var source = arguments[i];
  20. for (var key in source) {
  21. if (Object.prototype.hasOwnProperty.call(source, key)) {
  22. target[key] = source[key];
  23. }
  24. }
  25. }
  26. return target;
  27. };
  28.  
  29. var actions = mapActions([
  30. 'addNote',
  31. 'deleteNote',
  32. 'toggleFavorite'
  33. ]);
  34.  
  35. var methodsObj = _extends({},actions)
  36.  
  37. export default {
  38. computed: mapGetters([
  39. 'activeNote'
  40. ]),
  41. methods:methodsObj
  42. }
  43. </script>

其余两个子组件类似,相信大家已经明白了我的思路,具体代码如下:

NotesList.vue

  1. <template>
  2. <div id="notes-list">
  3.  
  4. <div id="list-header">
  5. <h2>Notes | coligo</h2>
  6. <div class="btn-group btn-group-justified" role="group">
  7. <!-- All Notes button -->
  8. <div class="btn-group" role="group">
  9. <button type="button" class="btn btn-default"
  10. @click="show = 'all'"
  11. :class="{active: show === 'all'}">
  12. All Notes
  13. </button>
  14. </div>
  15. <!-- Favorites Button -->
  16. <div class="btn-group" role="group">
  17. <button type="button" class="btn btn-default"
  18. @click="show = 'favorites'"
  19. :class="{active: show === 'favorites'}">
  20. Favorites
  21. </button>
  22. </div>
  23. </div>
  24. </div>
  25. <!-- render notes in a list -->
  26. <div class="container">
  27. <div class="list-group">
  28. <a v-for="note in filteredNotes"
  29. class="list-group-item" href="#"
  30. :class="{active: activeNote === note}"
  31. @click="updateActiveNote(note)">
  32. <h4 class="list-group-item-heading">
  33. {{note.text.trim().substring(0, 30)}}
  34. </h4>
  35. </a>
  36. </div>
  37. </div>
  38.  
  39. </div>
  40. </template>
  41.  
  42. <script>
  43.  
  44. import { mapGetters, mapActions } from 'vuex'
  45.  
  46. // ES5 实现扩展运算符...
  47. var _extends = Object.assign || function(target) {
  48. for (var i = 1; i < arguments.length; i++) {
  49. var source = arguments[i];
  50. for (var key in source) {
  51. if (Object.prototype.hasOwnProperty.call(source, key)) {
  52. target[key] = source[key];
  53. }
  54. }
  55. }
  56. return target;
  57. };
  58.  
  59. var getters = mapGetters([
  60. 'activeNote'
  61. ]);
  62.  
  63. var filters = {
  64. filteredNotes: function () {
  65. if (this.show === 'all'){
  66. return this.$store.state.notes
  67. } else if (this.show === 'favorites') {
  68. return this.$store.state.notes.filter(note => note.favorite)
  69. }
  70. }
  71. }
  72.  
  73. var actions = mapActions(['updateActiveNote'])
  74.  
  75. var computedObj = _extends({},getters,filters);
  76.  
  77. var methodsObj = _extends({},actions);
  78.  
  79. export default {
  80. data () {
  81. return {
  82. show: 'all'
  83. }
  84. },
  85. computed:computedObj,
  86. methods:methodsObj
  87. }
  88. </script>

Editor.vue

  1. <template>
  2. <div id="note-editor">
  3. <textarea
  4. :value="activeNoteText"
  5. @input="editNote"
  6. class="form-control">
  7. </textarea>
  8. </div>
  9. </template>
  10.  
  11. <script>
  12.  
  13. import { mapGetters, mapActions } from 'vuex'
  14.  
  15. export default {
  16. computed:mapGetters(['activeNoteText']),
  17. methods:mapActions(['editNote'])
  18. }
  19. </script>

Webpack 模板

直接使用 vue-cli 的 webpack 模板就会简单很多,可以直接解析扩展运算符,代码也会比较简洁。我就不多说了,直接贴上 github 的地址,大家有不懂的可以看一下:https://github.com/nzbin/notes-app-vuejs2-vuex2

总结

终于写完了这篇文章,感慨颇多。这个例子比较典型,学习的人很多,可能我并不是第一个重写这个案例的人,我只是与大家分享我的一些心得。顺便提一句,为了重写这个示例并解决遇到的这些小问题,我们可能要使用很多资源,比如 github、codePen、stackoverflow、npm 官网、babel 官网、vuejs 官网、vuex 官网、博客等等。回头再想想 Vue 到底是什么,一个对象,没错,一个集合了很多属性和方法的对象。为什么要强调面向对象的重要性,可能这就是最好的阐释,包括 jQuery、react、其它框架等等。一旦遇到问题,在控制台打印 Vue 实例,反复查看其属性可能很有帮助。

最后发个预告,下一篇文章我想探讨一下面向对象的 CSS,分析几个优秀的 UI 框架,我相信每个人都可以书写属于自己的 CSS 框架。

探索 vuex 2.0 以及使用 vuejs 2.0 + vuex 2.0 构建记事本应用的更多相关文章

  1. 探索 vuex 2.0 以及使用 vuejs 2.0 + vuex 2.0 构建记事本应用23

    前言 首先说明这并不是一个教程贴,而记事本应用是网上早有的案例,对于学习 vuex 非常有帮助.我的目的是探索 vuex 2.0 ,然后使用 vuejs 2.0 + vuex 2.0 重写这个应用,其 ...

  2. vuejs学习——vue+vuex+vue-router项目搭建(三)

    前言 vuejs学习——vue+vuex+vue-router项目搭建(一) vuejs学习——vue+vuex+vue-router项目搭建(二) 为什么用vuex:组件之间的作用域独立,而组件之间 ...

  3. vuejs学习——vue+vuex+vue-router项目搭建(二)

    前言 最近比较忙,所有第二章发布晚了,不好意思各位. vuejs学习——vue+vuex+vue-router项目搭建(一) 中我们搭建好了vue项目,我相信大家已经体验了vue其中的奥妙了,接下来我 ...

  4. vue2.0项目实战(5)vuex快速入门

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

  5. 探索 ASP.Net Core 3.0系列三:ASP.Net Core 3.0中的Service provider validation

    前言:在本文中,我将描述ASP.NET Core 3.0中新的“validate on build”功能. 这可以用来检测您的DI service provider是否配置错误. 具体而言,该功能可检 ...

  6. 探索ASP.Net Core 3.0系列六:ASP.NET Core 3.0新特性启动信息中的结构化日志

    前言:在本文中,我将聊聊在ASP.NET Core 3.0中细小的变化——启动时记录消息的方式进行小的更改. 现在,ASP.NET Core不再将消息直接记录到控制台,而是正确使用了logging 基 ...

  7. webpack + vuejs(都是1.0的版本) 基本配置(一)

    开始之前 本文包含以下技术,文中尽量给与详细的描述,并且附上参考链接,读者可以深入学习: 1.webpack12.Vue.js13.npm4.nodejs —- 这个就不给连接了,因为上面的连接都是在 ...

  8. vue2.0读书笔记3 - router、vuex

    1.vue的应用场景.优势.劣势 优势 通常情况下,运行时效率更高:一个事件循环仅一次视图更新,无频繁的DOM操作: 数据与视图分离,通过管理数据流,控制页面的展现,便于维护.且高效: 数据双向绑定, ...

  9. Spring Boot 2.0系列文章(五):Spring Boot 2.0 项目源码结构预览

    关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/04/15/springboot2_code/ 项目结构 结构分析: Spring-boot-pr ...

随机推荐

  1. React组件开发(二)表达式

    var obj = { name:"xiaoming", age:"18" } var Hello= React.createClass({ render:fu ...

  2. Struts2学习笔记⑥

    在微信读书上在看一本李刚写的Struts 2.X权威指南 (好像叫这个)的书,可以看得出来作者的开发经验还是很充足的,但是觉得他的尺度和顺序没有把握好,他自己说拦截器是数据校验.国际化的基础-完了还把 ...

  3. (1)写给Web初学者的教案-----学习Web的知识架构

    1:学习Web的知识架构 前文中我们简单的介绍了一些关于Web的基本知识,这里任老师再次强调一下凡是用浏览器打开的网站我们就称之为Web应用程序(B/S结构).除此之外其它需要下载安装的软件或是手机  ...

  4. adt的问题An internal error has occurred. After scene creation, #init() must be called

    这个问题困扰了我好久,我也尝试去百度.google无济于事啊,让我寝食难安,太难受了,我把它贴出来,希望后人不绕弯子... 解决办法: 即可,解决这一个问题,现在酣畅淋漓,挥洒自如的capy代码了

  5. UWP--页面传值

    //匿名对象 private void Button1_OnClick(object sender, RoutedEventArgs e) { , name = "LBI" }); ...

  6. 存储结构与邻接矩阵,深度优先和广度优先遍历及Java实现

    如果看完本篇博客任有不明白的地方,可以去看一下<大话数据结构>的7.4以及7.5,讲得比较易懂,不过是用C实现 下面内容来自segmentfault 存储结构 要存储一个图,我们知道图既有 ...

  7. React中父组件与子组件之间的数据传递和标准化的思考

    React中父组件与子组件之间的数据传递的的实现大家都可以轻易做到,但对比很多人的实现方法,总是会有或多或少的差异.在一个团队中,这种实现的差异体现了每个人各自的理解的不同,但是反过来思考,一个团队用 ...

  8. JAVA-Servlet-ServletConfig 与 ServletContext 的区别

    什么是ServletConfig? Servlet容器初始化一个servlet对象时,会为这个servlet对象创建一个servletConfig对象.在servletConfig对象中包含了serv ...

  9. python 附加作业01

    题目1: 画方块 输入样例: 10 a 输出样例: 代码: N=eval(input()) c=input() for i in range(N): for j in range(N): print( ...

  10. java之泛型解说

    1.集合中只能装入引用数据类型,不能装入基本数据类型.如,装入int类型的数值123会自动装箱. 2.开发人员装入集合的数据类型不确定,所以它被设计成可以装入所有的Object. 3.新的问题产生,装 ...