你也許不知道的Vuejs - 使用ES6快樂的玩耍

yugasun發表於2018-03-01

by yugasun from yugasun.com/post/you-ma… 本文可全文轉載,但需要保留原作者和出處。

上一篇中我們已經學會使用 babel 將 ES6 轉化為 ES5 了,並且展示了一些 ES6 程式碼,這一篇將重點聊聊 ES6 在 Vuejs 專案中一些部分應用。

什麼是ES6

摘自 ECMAScript 6 簡介

大家習慣將 ECMAScript 6.0 簡稱為 ES6,它是 Javascript 語言的下一代標準,它的目標,是使得 Javascript 語言可以用來編寫複雜的大型應用程式,成為企業級開發語言。

說是下一代,其實早在 2015 年 6月就正式釋出了(所以又稱 ES2015),截止今日已經快3年了,很快 ES7/ES8 都要出來了,所以作為前端開發者,學習 ES6 已經是個必然命題了。不要再問有沒有必要學習之類的問題了。

關於 ES6 的基礎知識,推薦 阮一峰 老師的 ECMAScript 6 入門,看完你就會覺得,並不難。至於有些後端的朋友跟我聊到說 ES6 完全不明白,那是因為你們思想還停留在過去 jQuery 的時代,那個時候只需要隨便複製幾段程式碼,然後寫幾個 js 函式,就可以搞定很多後端模板頁面了,但是今非昔比了,所以如果你們想深入瞭解前端,做一個全棧工程師,還是靜下心來,好好閱讀這篇 ES6 教程。程式碼這東西,不只是需要會用,更重要的是需要知其所以然。就像你一個 python 同事看到一段程式碼時問我,為啥 JS 引用一個模組時是 import Vue form 'vue',而不是 from 'vue' import Vue 一樣,這個我是沒法解釋的,因為ES6語言的規範就是這樣的啊~

模組的定義和引入

複製一份上一篇中的專案,在 src 目錄下建立一個 utils.js 檔案,內容如下:

/**
 * 簡單的深拷貝實現,個人經常這麼使用
 * 這裡obj中不能包含特殊型別值:undefined,NaN,function型別值
 * @param {object} obj
 */
export function deepClone(obj) {
  return JSON.parse(JSON.stringify(obj));
}

/**
 * log 函式的二次封裝,這裡只是為了演示
 * @param {any} content
 */
export function log(content) {
  console.log(`[Yuga log]: ${JSON.stringify(content)}`);
}
複製程式碼

它就是通過 export 暴露出了兩個工具函式。

修改 app.js 程式碼進行引入使用:

import './styles/app.scss';
import Vue from 'vue';
import userinfo from './info';
import { deepClone } from './utils';
import * as utils from './utils';
import hello1 from './hello1.vue';
import hello2 from './hello2.vue';

new Vue({
  el: "#app",
  template: '<div><hello1 v-bind:info="userinfo"/><hello2/></div>',
  data () {
    return {
      userinfo: deepClone(userinfo)
    }
  },
  components: {
    hello1,
    hello2
  },
  created () {
    // 你會發現這裡也同時改變了源原資料 info ,
    // 所以需要用到深拷貝源資料賦值給 data 中的 info
    this.userinfo.name = 'yugasun111'
    utils.log(userinfo)
  }
});
複製程式碼

上面分別演示了 5 種模組引入方法,無論是哪一種,其實這個要看該模組是如何 export 的,對於任何一個 npm 模組,如果它支援 ES6 方式引入,它一般都會使用 exportexport default 關鍵字暴露出一個物件(任何一個js資料型別),只不過 export default 指定了預設輸出,這樣使用者就不用閱讀介面文件,就可以按照自己需要載入該模組,並修改為自己喜歡的名稱來使用。而對於有些模組(如上面的 utils.js)可以通過 export 關鍵字來實現多個子模組的輸出,這樣使用者就可以根據個人需要來引入對應的子模組,前提是得知道其內部子模組名稱才行。 而 import 'normalize.css' 相當於全域性引入了,將該模組的所有內容一併引入。

更多細節講解,建議閱讀這篇文章: ES6 Module 的語法

這點對於 require 的引入方式,是沒法做到的,而著名的 tree-shaking 功能就是依賴 ES6 的這種模組系統。

非同步程式設計

Promise 是非同步程式設計的一種解決方案,比傳統的解決方案(回撥函式)更合理和更強大。它由社群最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了 Promise 物件。詳細 Promise 介紹可以閱讀這篇博文:非同步程式設計之 Promise; ES2017 標準又引入了 async 函式,是的非同步操作變得更加方便。更詳細的介紹在這裡:async 函式

有的人說有了 async 函式 就不再需要 Promise 了,我覺得是不對的,因為很多非同步模組都是先基於 Promise 封裝,然後才能經過 async/await 函式來操作的。當然把兩者結合起來用,可以完成更加強大的非同步程式設計操作,Vuejs 程式碼也變得更加靈活、簡介和強大。

這裡既然是 Vuejs 實戰,我就不講它們的基礎用法了,來看看如何用在實際專案中吧~

要想在專案中使用 async/await 函式,就必須有相關 babel 外掛來支援,因為 async/await 語法屬於 ES2017 規範的內容,所以需要引入 babel-preset-stage-2 外掛來轉化,當然還有 stage-0, stage-1, stage-3 等,這裡參考 vue-cli 常用 stage-2,直接安裝:

npm install babel-preset-stage-2 babel-plugin-transform-runtime --save-dev
複製程式碼

然後在 .babelrc 檔案中新增配置:

{
  "presets": [ "env", "stage-2" ],
  "plugins": [
    "transform-runtime"
  ]
}
複製程式碼

現在我們的語言支援,配置好了,現在開始使用。

上面的程式碼中,我們的 userinfo 是我們通過 info.js 硬編碼,然後在 app.js 中引入,通過 props 傳遞給了 hello1.vue 元件,下面我們通過請求介面方式來獲取。

先安裝 vue-axios-plugin 外掛,來提供網路請求服務:

npm install vue-axios-plugin --save
複製程式碼

然後在 app.js 中匯入並使用:

// 引入外掛
import VueAxiosPlugin from 'vue-axios-plugin';
// 使用外掛
Vue.use(VueAxiosPlugin)
複製程式碼

這樣在應用中的元件中都會在 this 上繫結一個 $http 屬性,它由 getpost 方法,具體使用文件:vue-axios-plugin文件

然後在 hello1.vue 中建立請求方法 getUserInfo,並在 created 鉤子函式中呼叫:

// ...
methods: {
  async getUserInfo () {
    try {
      const res = await this.$http.get('http://yapi.demo.qunar.com/mock/4377/userinfo');
      this.info = res.data
    } catch (e) {
      console.log(e);
    }
  }
},
// ...
複製程式碼

重新執行專案,稽核元素,發現成功發起了 GET 請求,並顯示了介面資料。

注意:其實 Vuejs 的 methods 屬性中所有的方法都可以定義為 async函式 來使用,包括 Vuejs 的生命週期函式,比如 created

可以發現使用 async/await 語法比使用 Promise 的鏈式語法簡潔多了,而且閱讀性更強了。

高階非同步程式設計

因為本人工作主要是在大資料視覺化,經常在繪製某張定製化圖形時,需要同時依賴多個介面(不要問我為什麼會有這麼多介面?為啥後端不一次性傳給我?因為我不想跟他們撕逼,╮(╯▽╰)╭哎),而且必須在這幾個請求都處理完後,才能開始處理資料,多的時候一張圖行依賴6個介面,當然我可以在一個 async函式 中寫6行 await 語法的請求,然後逐個處理,但是這樣效率太低了,但是這6個請求是可以並行執行的。這時就可以結合 Promise.all 高階方法來處理。這樣我可以同時發起六個請求,然後統一處理介面返回資料。為了方便,這裡演示同時請求2個url,多個的是一樣的。我們再在 hello1.vue 中新增一個 get2UserInfo 方法,然後在 created 中呼叫下,更新後的 hello1.vue 如下:

<template>
  <div>
    <h1>{{ msg }}</h1>
    <a v-bind:href="info.site">{{ info.name }}</a><br>
    <a v-bind:href="user1.site">{{ user1.name }}</a><br>
    <a v-bind:href="user2.site">{{ user2.name }}</a><br>
  </div>
</template>
<script>
export default {
  name: 'hello1',
  data () {
    return {
      msg: 'Hello Vue.js',
      info: {},
      user1: {},
      user2: {}
    }
  },
  methods: {
    async getUserInfo () {
      try {
        const res = await this.$http.get('http://yapi.demo.qunar.com/mock/4377/userinfo');
        this.info = res.data
      } catch (e) {
        console.log(e);
      }
    },
    async get2UserInfo () {
      try {
        const res = await Promise.all([
          this.$http.get('http://yapi.demo.qunar.com/mock/4377/userinfo1'),
          this.$http.get('http://yapi.demo.qunar.com/mock/4377/userinfo2'),
        ])
        this.user1 = res[0].data;
        this.user2 = res[1].data;
      } catch (e) {
        console.log(e);
      }
    }
  },
  created () {
    this.getUserInfo();
    this.get2UserInfo();
  }
}
</script>
<style lang="scss" scoped>
h1 {
  color: $green;
}
</style>
複製程式碼

再次執行專案,可以發現頁面在初始化的時候同時發起了3個請求,並正常渲染出了介面資料。通過控制檯可以看到這三個請求是並行發起的。

注意:這裡的 Promise.all() 的引數是一個函式執行佇列,它們會同時發起,然後都請求成功後,會將佇列的每個任務的結果組裝成一個結果資料,然後返回。

總結

其實要把講ES6講完,實在是太難了,寫本書都很難講好,我這裡只能說是班門弄斧一下,結合我在 Vue 中的相關實踐,對相關功能做個簡短介紹,希望能起到引導的作用。不太瞭解的朋友,可以認真閱讀下文首提到的阮一峰老師的文章。接下來後面的文章將全部使用 ES6 的語法編寫程式碼。

原始碼在此

專題目錄

You-May-Not-Know-Vuejs

相關文章