Vue-Vuex
为什么研究Vue
作为一个前端开发,不会Vue简直呵呵。—— 某资深前端开发工程师曰。
前兆
我从入门前端,第一次亲密接触的就是React,直到最近team重组,新的leader在一次晨会中曰:“希望以后我们组的前端能使用Vue来开发。”
而我们组的前端,貌似就剩我自己了,另外一个还想着回归Java。
这让我这个React起家的小前端工程师,内心一万头XXX…奔腾而过。
当然Vue是很好的(要不然也不会在这BB了)。但是,脱离舒适区是一个痛苦的事情,脱离使用了很久的框架转战新的工具,搁谁都不可能很欢喜。但是,这是一个过程,需要慢慢适应……
突如其来的挑战
PO小姐姐来找我,问我手上活怎么样,是不是还在写bug。我说写的差不多了,呸,改的差不多了!
呵呵呵,那就给你介绍个新活呗。噼里啪啦一通讲。没懂啊。不要紧,我们上楼找帮手。(楼上新办公室,两层办公区,牛X的不要不要的),于是跟着PO小姐姐上楼吸甲醛。
我们组一直都是用Vue做项目啊,如果用React做,以后维护起来……
那好吧,我可以学习Vue。这就是我,学习动力十足的我。
下周二要Demo哦。MMP,今天周五了,回家学习两天,周二你就要一个Vue和H5结合的Demo,还是音乐视频互动感超强的那种?
呵呵哒。我一言不发。我只是来打酱油的,我是来学习的,我是你们的帮手而已,别把希望寄托在一个等待入门的小朋友身上,否则后果自负(客户搞事情,可别拿我当挡箭牌)。
周末,趁热来一发
Vue被赞的一B。至今没用过是不是太low B了。江湖传言,文档维护的相当流弊,何不前往一探究竟 。
于是开始了我学习新技能的一贯作风,啃文档!
好吧,get start,我的最爱,按部就班,照猫画虎,比葫芦画瓢……
来个Vue的ToDoList 吧。
脱了衣服,说干就干。

效果永远如此low B,css 是我不愿提及的痛!
很简单,输入框内填写内容,敲击回车,add one todo.
每个todo 都可以进行remove 和 update ,update 做的比较简单,依旧是输入框输入内容,然后直接点击要更新的todo 的update 按钮即可。
总数量 total 通过vuex 的getters 获得 todos.length。
思路是不是很清晰?道理是不是很简单?你别急,惊喜和意外在后面。
惊喜?!意外?!
写了这么久不上一点代码,有点耍流氓的赶脚。所以,走一波 code。
package.json
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
| { "name": "vue-todolist", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "peter", "license": "ISC", "dependencies": { "element-ui": "^2.0.10",//这货并没有用到,也就是研究的时候好奇,装上看看 "vue": "^2.5.13", "vue-router": "^3.0.1",//同上 "vuex": "^3.0.1" }, "devDependencies": { "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-es2015": "^6.24.1", "parcel-bundler": "^1.4.1",//这个可是最新流行的小鲜肉,近乎“0配置”的打包工具 "parcel-plugin-vue": "^1.5.0",//这个是与上结合食用的插件 "vue-template-compiler": "^2.5.13", "babel-preset-env": "^1.3.2", "babel-preset-stage-2": "^6.22.0" } }
|
.babelrc
1 2 3 4 5 6
| { "presets": [ "env", "stage-2" ] }
|
项目结构

因为太简单,压根也没有任何组件化的余地,所以,components文件夹形同虚设。
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
| import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex);
const store = new Vuex.Store({ state: { todos: [] }, mutations: { add: (state, todo) => { state.todos.push(todo); }, remove: (state, id) => { let index = state.todos.findIndex(v => v.id === id); state.todos.splice(index, 1); }, update: (state, update) => { let length = state.todos.length; for (let i = 0; i < length; i++) { let tmpTodo = state.todos[i]; if (tmpTodo.id === update.id) { state.todos[i].content = update.content; break; } } }, clear: state => state.todos = [] }, getters: { count: state => state.todos.length, } });
const commit = store.commit; const getters = store.getters;
export { store, commit, getters };
|
整体看下来是不是和 mobx 很像。这哪里是很像,这简直就是一模一样(这样说Vue粉儿们会不会砍死我)。
clear方法,我一开始很不由自主的使用了原始暴力而且一贯有效没问题的 = []。后来事实证明,没有一层不变的写法,只有一直变化套路。
这种原始暴力的方式,在我使用Redux 和mobx的过程中简直就是屡试不爽,怎么突然就哑火了呢。
问题是这样的:
虽然这种方式可以将store下state上的todos清空,但是并没有引起视图的变化。
Why?How?What?
划重点来啦!!!
这是clear之前的截图(有devtool就是爽,啥都一目了然、尽收眼底)

这是clear之后的图

咦,我擦,这是什么情况!!! BUG!!!BUG!!!BUG!!!我惊慌失措了!!!
淡定,遇到bug一定要冷静沉着思考分析……
领悟!!!

当我把模式调到查看组件状态时,借助devtool让我恍然大悟。
MMP,store.state.todos 是一个数组,而数组是一个Object,Object是引用类型数据,我将它传递给App的data.todos,等于是做了一次复制(浅拷贝),data保留的是对store.state.todos的引用。
是不是没明白,简单点吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| store.state.todos-> (old hash:12345)
data.todos-> (old hash:12345)
store.state.todos = [];//这一操作之后,惊喜和意外发生了
store.state.todos->(new hash:98765)
data.todos->(old hash:12345)
// 这里的hash是我为了说明情况儿 XJB 写的,别当真。
//也就是说,暴力赋值空数组时候,state下的todos已经不是原来的那个todos了, //这个新的数组在内存中占有一份新的地理位置, //而原来的那个old todos的引用,依旧被data保持着,停留在内存里,成为了bug的滋生地 //还是一种叫做内存泄漏的exception的源泉
|
App.vue
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 58
| <template> <div class="app"> <h1>Vue ToDoList</h1> <input @keyup.enter="add" @input="input" :value="content" placeholder="做点什么吧..."/> <a class="btn-a" @click="clear">Clear</a> <a style="font-size:15pt;">total:{{count}}</a> <ul> <li v-for="t in todos"> <span>{{t.content}}</span> <button @click="update(t.id)">update</button> <button @click="remove(t.id)">remove</button> </li> </ul> </div> </template>
<script> import { TodoItem } from "./components"; import { store, commit, getters } from "./store"; import todo from "./public/todo"; export default { name: "App", data() { return { todos: store.state.todos, content: "" }; }, computed: { count: () => getters.count }, methods: { input: function(e) { this.content = e.target.value; }, add: function(e) {//ES5写法多了几个字母,但是大大滴不一样哦 let t = new todo(); t.content = this.content; commit("add", t); this.content = ""; }, remove: id => {//注意这方法的写法与ES5写法的不同,很飘逸, commit("remove", id); }, update: function(id) { commit("update", { id, content: this.content }); this.content = ""; }, clear: () => commit("clear") } }; </script>
|
没办法,写到这种地步,只能咬牙继续,是谁说要好好学习的……
Vue 的基本属性 methods 的定义,让我百思不得其解了一小会儿。
比如,上述代码中看到的 add ,input update 这三个方法,我都采用了ES5的写法,通过function关键字定义函数;而remove和clear两个,我则使用了ES6的箭头函数写法。
同样是方法,怎么差距就这么大呢!!!
其实一开始,我清一色的写的箭头函数,但是发现,有问题,问题很简答,this = undefined。
我就纳闷了,这尼玛又是什么鬼,也来不及再去Google了,之前乱投医,试一试吧,换成了ES5,嘿,无药而治,立竿见影,奇迹般地好了!
真相只有一个! —— 柯南。
如同没有无缘无故的爱,也没有无缘无故的恨那般,没有可能我随便一改写法,就奇迹般的好了!
肯定有原因!
箭头函数表达式的语法比函数表达式更短,并且不绑定自己的this,arguments,super或 new.target。这些函数表达式最适合用于非方法函数,并且它们不能用作构造函数。 MDN
有没有很清楚很明白,没有吧。
作为一个在React里漫天飞舞箭头函数绑定this的小前端而言,这么机器的翻译,我才不会当真呢!

廖雪峰
看了廖老师的文章,赶紧换了套路—–>

MMP,这就是不问所以然,就XJB写的下场!
仔细想一想吧。

能够调试是一件多么幸福的事情,一目了然的看到,function函数内的this指向的是整个VueComponent,而在箭头函数内,毛都没有!为啥呢?廖雪峰老师说的很明白了,就是说,箭头函数的this指向调用方的,而我将add方法通过@符号绑定给了input标签,而且在函数加载的过程中,input标签还没有挂载完毕呢,所以,箭头函数在那一瞬间,毛也咩有捕获到,所以它的this是个undefined,而function函数就不一样了,它在初始化的时候,捕捉到的是整个对象本身(VueComponent),所以……
虽然不知道这种理解方式对不对,但是暂时这样认为吧,回头再找大神求证。
尾声
罗里吧嗦写了这么多,时间都是23:17了,也该告一段落了,对于我这个Vue新生儿来说,这一天折腾的已经不少了。
希望能够在往后的日子里,和她相爱不相杀。