为什么研究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
{
"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
{
"presets": [
"env",
"stage-2"
]
}
复制代码
项目结构
因为太简单,压根也没有任何组件化的余地,所以,components文件夹形同虚设。
store
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);//重点来了,这玩意一定要在store创建之前use,要不然会出错哦。
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 = []//一会儿这里有惊喜和意外发生!!!
// clear: state => state.todos.splice(0, state.todos.length) 这行代码才是正解
},
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的引用。
是不是没明白,简单点吧:
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
<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新生儿来说,这一天折腾的已经不少了。
希望能够在往后的日子里,和她相爱不相杀。