Vue & Vuex 入門級研究與發現

PeterYuan發表於2019-02-18

為什麼研究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 吧。

脫了衣服,說幹就幹。

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"
    ]
}

複製程式碼

專案結構

structure

因為太簡單,壓根也沒有任何元件化的餘地,所以,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就是爽,啥都一目瞭然、盡收眼底)

before clear

這是clear之後的圖

after clear

咦,我擦,這是什麼情況!!! BUG!!!BUG!!!BUG!!!我驚慌失措了!!!

淡定,遇到bug一定要冷靜沉著思考分析……

領悟!!!

totally clear

當我把模式調到檢視元件狀態時,藉助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的小前端而言,這麼機器的翻譯,我才不會當真呢!

lxf

廖雪峰

看了廖老師的文章,趕緊換了套路—–>

MMP

MMP,這就是不問所以然,就XJB寫的下場!

仔細想一想吧。

happy

能夠除錯是一件多麼幸福的事情,一目瞭然的看到,function函式內的this指向的是整個VueComponent,而在箭頭函式內,毛都沒有!為啥呢?廖雪峰老師說的很明白了,就是說,箭頭函式的this指向呼叫方的,而我將add方法通過@符號繫結給了input標籤,而且在函式載入的過程中,input標籤還沒有掛載完畢呢,所以,箭頭函式在那一瞬間,毛也咩有捕獲到,所以它的this是個undefined,而function函式就不一樣了,它在初始化的時候,捕捉到的是整個物件本身(VueComponent),所以……

雖然不知道這種理解方式對不對,但是暫時這樣認為吧,回頭再找大神求證。

尾聲

羅裡吧嗦寫了這麼多,時間都是23:17了,也該告一段落了,對於我這個Vue新生兒來說,這一天折騰的已經不少了。

希望能夠在往後的日子裡,和她相愛不相殺。

相關文章