Vue.JS 開發常見問題集錦

weixin_34208283發表於2018-10-22

由於公司的前端開始轉向 Vue.JS,最近開始使用這個框架進行開發,遇到一些問題記錄下來,以備後用。
主要寫一些 官方手冊 上沒有寫,但是實際開發中會遇到的問題,需要一定知識基礎。

涉及技術棧

  • CLI: Vue-CLI
  • UI: Element
  • HTML: Pug(Jade)
  • CSS: Less
  • JavaScript: ES6

polyfill 與 transform-runtime

首先,vue-cli 為我們自動新增了 babel-plugin-transform-runtime 這個外掛,該外掛多數情況下都運作正常,可以轉換大部分ES6語法。
但是,存在如下兩個問題:

1、非同步載入元件時,會產生 polyfill 程式碼冗餘
2、不支援對全域性函式與例項方法的polyfill
兩個問題的原因均歸因於 babel-plugin-transform-runtime 採用了沙箱機制來編譯我們的程式碼(即:不修改宿主環境的內建物件)。

由於非同步元件最終會被編譯為一個單獨的檔案,所以即使多個元件中使用了同一個新特性(例如:Object.keys()),那麼在每個編譯後的檔案中都會有一份該新特性的 polyfill 拷貝。如果專案較小可以考慮不使用非同步載入,但是首屏的壓力會比較大。
  不支援全域性函式(如:Promise、Set、Map),Set 跟 Map 這兩種資料結構應該大家用的也不多,影響較小。但是 Promise 影響可能就比較大了。
  不支援例項方法(如:'abc'.include('b')、['1', '2', '3'].find((n) => n < 2) 等等),這個限制幾乎廢掉了大部分字串和一半左右陣列的新特性。

一般情況下 babel-plugin-transform-runtime 能滿足大部分的需求,當不滿足需求時,推薦使用完整的 babel-polyfill。

替換 babel-polyfill
首先,從專案中移除 babel-plugin-transform-runtime
  解除安裝該依賴:

npm un babel-plugin-transform-runtime -D

修改 babel 配置檔案

// .babelrc
{
  //...
  "plugins": [
    // - "transform-runtime"
  ]
  //...
}

然後,安裝 babel-polyfill 依賴:

npm i babel-polyfill -D

最後,在入口檔案中匯入

// src/main.js
import 'babel-polyfill'

ES6 import 引用問題

在 ES6 中,模組系統的匯入與匯出採用的是引用匯出與匯入(非簡單資料型別),也就是說,如果在一個模組中定義了一個物件並匯出,在其他模組中匯入使用時,匯入的其實是一個變數引用(指標),如果修改了物件中的屬性,會影響到其他模組的使用。
  通常情況下,系統體量不大時,我們可以使用 JSON.parse(JSON.stringify(str)) 簡單粗暴地來生成一個全新的深度拷貝的 資料物件。不過當元件較多、資料物件複用程度較高時,很明顯會產生效能問題,這時我們可以考慮使用 Immutable.js。

鑑於這個原因,進行復雜資料型別的匯出時,需要注意多個元件匯入同一個資料物件時修改資料後可能產生的問題。 此外,模組定義變數或函式時即便使用 let 而不是 const,在匯入使用時都會變成只讀,不能重新賦值,效果等同於用 const 宣告。

在 Vue 中使用 Pug 與 Less

安裝依賴

Vue 中使用 vue-loader 根據 lang 屬性自動判斷所需要的 loader,所以不用額外配置 Loader,但是需要手動安裝相關依賴:

npm i pug -D
npm i less-loader -D

還是相當方便的,不用手動修改 webpack 的配置檔案新增 loader 就可以使用了

使用 pug 還是 pug-loader?sass 兩種語法的 loader 如何設定? --- 請參考 前處理器 · vue-loader

使用

<!-- xxx.vue -->
<style lang="less">
  .action {
    color: #ddd;
      ul {
        overflow: hidden;
        li {
          float: left;
        }
      }
  }
</style>
<template lang="pug">
  .action(v-if='hasRight')
    ul
      li 編輯
      li 刪除
</template>
<script>
  export default {
    data () {
      return {
        hasRight: true
      }
    }
  }
</script>

定義全域性函式或變數

許多時候我們需要定義一些全域性函式或變數,來處理一些頻繁的操作(這裡拿 AJAX 的異常處理舉例說明)。但是在 Vue 中,每一個單檔案元件都有一個獨立的上下文(this)。通常在異常處理中,需要在檢視上有所體現,這個時候我們就需要訪問 this 物件,但是全域性函式的上下文通常是 window,這時候就需要一些特殊處理了。

簡單粗暴型

最簡單的方法就是直接在 window 物件上定義一個全域性方法,在元件內使用的時候用 bind、call 或 apply 來改變上下文。
  定義一個全域性異常處理方法:

// errHandler.js
window.errHandler = function () { // 不能使用箭頭函式
  if (err.code && err.code !== 200) {
    this.$store.commit('err', true)
  } else {
    // ...
  }
}

在入口檔案中匯入:

// src/main.js
import 'errHandler.js'
  在元件中使用:

// xxx.vue
export default {
  created () {
    this.errHandler = window.errHandler.bind(this)
  },
  method: {
    getXXX () {
      this.$http.get('xxx/xx').then(({ body: result }) => {
        if (result.code === 200) {
          // ...
        } else {
          this.errHandler(result)
        }
      }).catch(this.errHandler)
    }
  }
}

優雅安全型

在大型多人協作的專案中,汙染 window 物件還是不太妥當的。特別是一些比較有個人特色的全域性方法(可能在你寫的元件中幾乎處處用到,但是對於其他人來說可能並不需要)。這時候推薦寫一個模組,更優雅安全,也比較自然,唯一不足之處就是每個需要使用該函式或方法的元件都需要進行匯入。
  使用方法與前一種大同小異,就不多作介紹了。 ̄

自定義路徑別名

可能有些人注意到了,在 vue-cli 生成的模板中在匯入元件時使用了這樣的語法:

import Index from '@/components/Index'

這個 @ 是什麼東西?後來改配置檔案的時候發現這個是 webpack 的配置選項之一:路徑別名。
  我們也可以在基礎配置檔案中新增自己的路徑別名,比如下面這個就把 ~ 設定為路徑 src/components 的別名:

// build/webpack.base.js
{
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
      '~': resolve('src/components')
    }
  }
}

然後我們匯入元件的時候就可以這樣寫:

// import YourComponent from 'YourComponent'
// import YourComponent from './YourComponent'
// import YourComponent from '../YourComponent'
// import YourComponent from '/src/components/YourComponent'
import YourComponent from '~/YourComponent'

既解決了路徑過長的麻煩,又解決了相對路徑的煩惱,方便很多吧!

CSS 作用域與模組

元件內樣式

通常,元件中 標籤裡的樣式是全域性的,在使用第三方 UI 庫(如:Element)時,全域性樣式很可能影響 UI 庫的樣式。我們可以通過新增 scoped 屬性來使 style 中的樣式只作用於當前元件:

<style lang="less" scoped>
  @import 'other.less';
  .title {
    font-size: 1.2rem;
  }
</style>

在有 scoped 屬性的 style 標籤內匯入其他樣式,同樣會受限於作用域,變為元件內樣式。複用程度較高的樣式不建議這樣使用。 另,在元件內樣式中應避免使用元素選擇器,原因在於元素選擇器與屬性選擇器組合時,效能會大大降低。 --- 兩種組合選擇器的測試:classes selector,elements selector

匯入樣式

相對於 style 使用 scoped 屬性時的元件內樣式,有時候我們也需要新增一些全域性樣式。當然我們可以用沒有 scoped 屬性的 style 來寫全域性樣式。但是相比較,更推薦下面這種寫法:

/* 單獨的全域性樣式檔案 */
/* style-global.less */
body {
  font-size: 10px;
}
.title {
  font-size: 1.4rem;
  font-weight: bolder;
}

然後在入口檔案中匯入全域性樣式:

// src/main.js
import 'style-global.less'

獲取表單控制元件值

通常我們可以直接使用 v-model 將表單控制元件與資料進行繫結,但是有時候我們也會需要在使用者輸入的時候獲取當前值(比如:實時驗證當前輸入控制元件內容的有效性)。

這時我們可以使用 @input 或 @change 事件繫結我們自己的處理函式,並傳入 $event 物件以獲取當前控制元件的輸入值:

<input type='text' @change='change($event)'>
change (e) {
  let curVal = e.target.value
  if (/^\d+$/.test(curVal)) {
    this.num = +curVal
  } else {
    console.error('%s is not a number!', curVal)
  }
}

當然,如果 UI 框架採用 Element 會更簡單,它的事件回撥會直接傳入當前值。

v-for 的使用 tips

v-for 指令很強大,它不僅可以用來遍歷陣列、物件,甚至可以遍歷一個數字或字串。

基本語法就不講了,這裡講個小 tips:

索引值

在使用 v-for 根據物件或陣列生成 DOM 時,有時候需要知道當前的索引。我們可以這樣:

<ul>
  <li v-for='(item, key) in items' :key='key'> {{ key }} - {{ item }}
</ul>

但是,在遍歷數字的時候需要注意,數字的 value 是從 1 開始,而 key 是從 0 開始:

<ul>
  <li v-for='(v, k) in 3' :key='k'> {{ k }}-{{ v }} 
  <!-- output to be 0-1, 1-2, 2-3 -->
</ul>

2.2.0+ 的版本里,當在元件中使用 v-for 時,key 現在是必須的。

模板的唯一根節點

與 JSX 相同,元件中的模板只能有一個根節點,即下面這種寫法是 錯誤 的:

<template>
  <h1>Title</h1>
  <article>Balabala...</article>
</template>

我們需要用一個塊級元素把他包裹起來:

<template>
  <div>
    <h1>Title</h1>
    <article>Balabala...</article>
  </div>
</template>

專案路徑配置

由於 vue-cli 配置的專案提供了一個內建的靜態伺服器,在開發階段基本不會有什麼問題。但是,當我們把程式碼放到伺服器上時,經常會遇到靜態資源引用錯誤,導致介面一片空白的問題。

這是由於 vue-cli 預設配置的 webpack 是以站點根目錄引用的檔案,然而有時候我們可能需要把專案部署到子目錄中。

我們可以通過 config/index.js 來修改檔案引用的相對路徑:

  build.assetsSubDirectory: 'static'
  build.assetsPublicPath: '/'

  dev.assetsSubDirectory: 'static'
  dev.assetsPublicPath: '/'

我們可以看到匯出物件中 build 與 dev 均有 assetsSubDirectory、assetsPublicPath 這兩個屬性。

其中 assetsSubDirectory 指靜態資原始檔夾,也就是打包後的 js、css、圖片等檔案所放置的資料夾,這個預設一般不會有問題。

assetsPublicPath 指靜態資源的引用路徑,預設配置為 /,即網站根目錄,與 assetsSubDirectory 組合起來就是完整的靜態資源引用路徑 /static。

寫到這裡解決方法已經很明顯了,只要把根目錄改為相對目錄就好了:

  build.assetsSubDirectory: 'static'
  build.assetsPublicPath: './'

沒錯!就是一個 . 的問題。

相關文章