為什麼研究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新生兒來說,這一天折騰的已經不少了。
希望能夠在往後的日子裡,和她相愛不相殺。