顯示具有 Vue 標籤的文章。 顯示所有文章
顯示具有 Vue 標籤的文章。 顯示所有文章

2019年12月28日

[Vue] 整合 Vue style guide, eslint-plugin-vue 和 VSCode

Vue Style Guide 中對於 Vue 專案的程式風格提供了許多建議,但有些時候在撰寫程式時還需要額外留意這些撰寫風格反而變成一種負擔,好在這時候 Vue 提供了好用的 eslint-plugin-vue (@vue/cli-plugin-eslint),透過 eslint 可以幫我們檢查哪些程式碼中的內容是不符合 Style Guide 所建議的,並且予以修正。
同時搭配上 VSCode 後,可以在存檔後自動根據 eslint 的建議來重新編排程式碼,不只省下了許多需要調整撰寫風格的認知負擔,更重要的是省下了許多時間,可以專注在程式開發上。
在這篇文章中就說明如何根據 Vue Style Guide 一併整合 eslint-plugin-vue 與 VSCode。

安裝 eslint-plugin-vue

建立專案

這裡快速的透過 Vue CLI 的工具快速建立專案:
# 這裡使用的 Vue CLI 版本為 4.2.3
$ vue create vue-eslint-sandbox

安裝用於 Vue 的 eslint 設定檔套件

接著透過 Vue CLI 安裝 eslint-plugin-vue:
# 若要直接透過 npm 安裝,可以參考官方說明
$ vue add @vue/cli-plugin-eslint
接著會請你選擇要使用的 ESLint 設定檔,這裡可以根據團隊或自己撰寫 JavaScript 的習慣去選擇,舉例來說這裡選擇 Standard(也就是 Standard JS 的風格規範):
Imgur
接著會進一步詢問有沒有需要額外個功能。這裡我只選第一項:
eslint-plugin-vue

設定 eslint 並修改程式碼

預設 cli-plugin-eslint 會把 ESLint 的設定檔寫在 package.json 中的 eslintConfig 欄位,像是這樣:
// package.json
{
  "name": "vue-eslint-sandbox",
  // ...
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended",
      "@vue/standard"
    ],
    "parserOptions": {
      "parser": "babel-eslint"
    },
    "rules": {}
  },
  // ...
}
Vue 的程式碼撰寫風格中可以分成幾種不同的嚴謹度,分別是 base, essential, strongly-recommended, 和 recommended
{
  "extends": "plugin:vue/base",         // 受到的規範最少
  "extends": "plugin:vue/essential",
  "extends": "plugin:vue/strongly-recommended",
  "extends": "plugin:vue/recommended"   // 最嚴謹,完全依照建議
}
簡單來說,plugin:vue/recommended 會套用幾乎所有在 Vue Style Guide 中的建議,因此會使用到最多規則,也最不用自己動腦調整,其次是 plugin:vue/strongly-recommended,如果你希望受到 Vue Style Guide 的規範最少,則可以選 plugin:vue/base
關於每一項設定的詳細規則可以參考 eslint-plugin-vue rules 的說明。
可以選擇你希望遵守的嚴謹程度,修改在 package.json 中的 eslintConfig 中的 extends 當中的值。

補充:使用 eslint 設定檔(可略過)

ESLint 除了可以解析 package.json 中對於 ESLint 的設定外,也可以建立額外的 ESLint 設定檔。因此,如果你不想把 ESLint 的設定存放在 package.json 中的話,也可以把 eslintConfig 的欄位移除,額外在根目錄建立一隻 .eslintrc.js 的檔案,如同下面所述。
先在根目錄的地方建立 .eslintrc.js 檔,在這支檔案可以根據你的需求進行設定:
// ./.eslintrc.js
module.exports = {
  extends: [
    // 在這裡可以添加你想要使用的風格規範,例如:
    // 'eslint:recommended',
    'plugin:vue/recommended'
  ],
  rules: {
    // 在這裡可以針對特定的規則進行覆蓋或添加,例如
    // 'vue/no-unused-vars': 'error'
  }
}
其中 extends 的部分一樣有幾種選項可以根據自己或專案的需要進行調整:
// ./.eslintrc.js
{
  "extends": "plugin:vue/base",         // 受到的規範最少
  "extends": "plugin:vue/essential",
  "extends": "plugin:vue/strongly-recommended",
  "extends": "plugin:vue/recommended"   // 最嚴謹,完全依照建議
}
另外,因為這裡使用的是 Standard JS 的撰寫風格(vue/standard),並希望使用 ESLint 提供的建議(eslint:recommended),所以另外添加兩個規範;同時,因為專案中會使用到較新版本的 JavaScript 語法,因此需要定義 parserOptions 以使用比較新的 JavaScript 語法。最後的 .eslintrc.js 會長像這樣子:
// ./.eslintrc.js

module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: [
    // add more generic rulesets here, such as:
    'eslint:recommended',
    'plugin:vue/recommended',
    '@vue/standard'
  ],
  rules: {
    // override/add rules settings here, such as:
    // 'vue/no-unused-vars': 'error'
  },
  parserOptions: {
    parser: 'babel-eslint'
  }
}

執行 eslint 修改程式碼

剛剛我們透過 Vue CLI 安裝好 eslint-plugin-vue 之後,預設它會在 package.json 中添加了 lint 的指令,因此現在可以在終端機直接執行 npm run lint
$ npm run lint
執行後,會自動修改程式碼的撰寫風格,可以看到原本的檔案已經被調整成符合 Vue Style Guide 的建議,並且自動存檔
plugin-eslint-vue
同時,終端機也會列出所有不符合 Vue Style Guide 規範的部分,也就是需要人工調整的部分:
eslint-plugin-vue
這個警告很明顯的是在 Vue Style Guide 的 Prop definitions 中有提到,希望 Props 能盡量定義的詳細,這裡因為沒有告知 msg 這個屬性是 required 的,表示這個 props 有可能不存在,因此它會建議你要帶入預設值。
因此只需要把原本 HelloWord.vue 的程式碼根據建議加上預設值(default):
<!-- ./src/components/HelloWorld.vue  -->
<template>
  <!-- ... --->
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: {
      type: String,
      default: 'Hello Vue'
    }
  }
}
</script>
存檔後再執行一次 npm run lint 則顯示沒有任何錯誤!
eslint-plugin-vue
透過 npm run lint 之後,eslint-plugin-vue 會自動幫我們修正程式碼撰寫風格、HTML 標籤內的屬性順序、Vue 方法的排序等等都調整成 Vue Style Guide 的建議並存檔。但有些部分是它沒辦法自動處理的,這時候還是需要回去檢視這些部分。要留意的是,這些部分不去修正不代表你的程式碼會無法正確執行,而是沒有符合 Vue 的建議,而這些建議之所以存在,通常就是為了提升程式碼的可維護性和減少看能犯錯的機會。

整合 VS Code

最後,由於每次都要執行 npm run lint 稍微還有些麻煩,我們可以把 eslint 直接整合在 VSCode 中,當存檔的時候,就會自動轉換成建議的 Vue Style Guide 的撰寫風格。

安裝 Vue 和 Eslint 套件

Vetur

Vetur 套件可以算是在使用 VS Code 撰寫 Vue 時必配的套件,最明顯的是沒有它的話在撰寫 .vue 的時候程式碼不會有高亮的效果,但它實際上還做了很多其他的事。
vetur

ESLint

接著要安裝微軟官方提供的 ESLint 這個套件來將 VS Code 和 ESLint 整合在一起。
eslint

設定 settings.json

打開 settings.json 設定頁

首先進入 VSCode 的設定頁(MAC 可以按快捷鍵 CMD + ,):
imgur
因為現在 VSCode 預設是使用 UI 的方式進行設定,若想要直接編輯 settings.json 檔,可以在搜尋欄中搜尋 launch 然後點選在 settings.json 內編輯:
settings.json
接著就可以看到 settings.json 的檔案:
settings.json

添加相關設定

settings.json 中加入下面的設定,讓 eslint 可以針對 .vue 檔進行檢驗(預設只會針對 .js.jsx 的檔案):
// VSCode settings.json
{
  "eslint.alwaysShowStatus": true,
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact",
    "vue",
  ],
}
同時,如果你希望存檔時可以自動排版,則可以加上:
// VSCode settings.json
{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
}
另外,因為安裝了 ESLint,因此就不需要 Vetur 針對 template 提供的驗證,一樣在 settings.json 加上如下的設定:
// VSCode settings.json
{
  "vetur.validation.template": false
}
完整的 settings.json 檔案如下:
// VSCode settings.json
{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "eslint.alwaysShowStatus": true,
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact",
    "vue",
  ],
  "vetur.validation.template": false,
}

大功告成

到這裡就大功告成了,只要在你想按下存檔的時候,VS Code 就會自動根據 ESLint 中的設定重新排版程式畫面,若有警告或錯誤的程式碼,則會在程式碼下方以波浪符號提示:
eslint-plugin-vue

參考資源

2017年8月9日

如何在 Rails 中搭配 Turbolinks 使用 Vue (Use Vue in Rails with Turbolinks)

keywords: webpacker
說明如何在 Rails Turbolinks 中搭配使用 Vue。

Initialize the App

# initialize the app
rails new rails_sandbox_vue --database=postgresql --webpack=vue

# install package
bundle
yarn

Scaffold the app

# Scaffold the app
bin/rails g scaffold User name email

# Create database and migrate
bin/rails db:setup
bin/rails db:migrate

Create Vue Component

./app/javascript/ 中建立 vue component hello_vue.vue
<!--
./app/javascript/hello_vue.vue
-->

<template>
  <div>
    <h4>{{ message }}</h4>
    <ul>
      <li>Object: {{ obj }} </li>
      <li>Number: {{ num }} </li>
      <li>Array: {{ arr }} </li>
      <li>String: {{ str }} </li>
    </ul>
    </div>
</template>

<script>
export default {
  props: ['obj', 'arr', 'num', 'str'],
  data: function () {
    return {
      message: 'Hello, Vue and Turbolinks'
    }
  }
}
</script>

<style scoped>
h4 {
  font-size: 2em;
  text-align: center;
  color: steelblue;
}
</style>

Create Vue Adapter

./app/javascript/packs/
中建立 vue_adapter.js,在 import Vue 的地方要載入 vue.esm.js 可以 compile template 的版本。另外要把需要使用到的 Vue Component 在這裡執行註冊:
// ./app/javascript/packs/vue_adapter.js

// import Vue
import Vue from 'vue/dist/vue.esm.js'

// import your components
import HelloVue from 'hello-vue'

// register your components
Vue.component('hello-vue', HelloVue)

function VueConstructor () {
  let outlets = document.querySelectorAll('[data-vue-component-outlet]')
  outlets.forEach(function (outlet, index) {
    let id = outlet.getAttribute('data-vue-component-outlet')
    new Vue({
      el: '[data-vue-component-outlet=' + id + ']'
    })
  })
}

if (typeof Turbolinks !== 'undefined' && Turbolinks.supported) {
  document.addEventListener('turbolinks:load', VueConstructor)
} else {
  document.addEventListener('DOMContentLoaded', VueConstructor)
}
Notice:
-import 的 Vue 要匯入的是 vue.esm.js
-記得註冊使用到的 Vue Component

add vue_adapter in head

在 layouts/application.html.erb 中的 head 中加入 <%= javascript_pack_tag 'vue_adapter', 'data-turbolinks-track': 'reload' %>,以及 <%= stylesheet_pack_tag 'vue_adapter', 'data-turbolinks-track': 'reload' %>
<!-- ./app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
  <head>
    <title>RailsSandboxVue</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

    <%= stylesheet_pack_tag 'vue_adapter', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'vue_adapter', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>
Notice:
-要把 javascript_pack_tag 放在 head 當中
-如果 Vue 中有使用 SCSS 則需在 head 中再放入 stylesheet_pack_tag

Import Vue component in template

我們把 Vue 的組件載入 index.html.erb 中,data-vue-components-outlet 這個屬性是關鍵字,後面放要載入的 Vue 組件名稱:
<!-- ./app/views/users/index.html.erb -->

<!--  ...  -->

<!--  假設這是透過 controller 傳過來的資料  -->
<% @hello_message = {num: 1, str: '2', arr: [1, 2, 3], obj: {name: 'foo', age: 12}} %>

<!-- Import Vue Component -->
<div data-vue-components-outlet="hello-turbolinks">
  <hello-turbolinks
  :obj="<%= @hello_message[:obj].to_json %>"
  :arr="<%= @hello_message[:arr] %>"
  :str="<%= @hello_message[:str] %>"
  :num="<%= @hello_message[:num] %>"
  ></hello-turbolinks>
</div>
<!-- End of Import Vue Component -->

<%= link_to 'New User', new_user_path %>

完成

分別開兩個 terminal 到 app 目錄底下,分別執行:
bin/webpack-dev-server
bin/rails s
就可以看到 Vue Component 正確運作了。

使用 inline-template

依照上面的邏輯,我們可以透過 inline-template 的方式來使用 Vue, 這樣就可以把 Vue Template 的部分寫在 erb 中而不用拉到 .vue 裡面,以下是所建立的檔案:
<!--  ./app/javascript/hello-vue-inline.vue  -->

<script>
export default {
  props: ['obj', 'arr', 'num', 'str'],
  data: function () {
    return {
      message: 'Hello, Vue in inline-template.'
    }
  }
}
</script>

<style scoped>
h4 {
  font-size: 2em;
  text-align: center;
  color: purple;
}
</style>
// ./app/javascript/packs/vue_adapter.js
/**
 * 新增兩行
 * "import HelloVueInline from 'hello-vue-inline'"
 * "Vue.component('hello-vue-inline', HelloVueInline)"
 **/

...

// import your components
import HelloVue from 'hello-vue'
import HelloVueInline from 'hello-vue-inline' // 新增這行

// register your components
Vue.component('hello-vue', HelloVue)
Vue.component('hello-vue-inline', HelloVueInline)

...

<!--  ./app/views/users/index.html.erb  -->
<!--
 - 留意 Use Vue Component by inline-template 的部分
-->

<!--  ...  -->

<% @hello_message = {num: 1, str: '2', arr: [1, 2, 3], obj: {name: 'foo', age: 12}} %>

<!-- Import Vue Component -->
<div data-vue-component-outlet="v-hello-vue">
  <hello-vue
  :obj="<%= @hello_message[:obj].to_json %>"
  :arr="<%= @hello_message[:arr] %>"
  :str="<%= @hello_message[:str] %>"
  :num="<%= @hello_message[:num] %>"
  ></hello-vue>
</div>
<!-- End of Import Vue Component -->

<!-- Use Vue Component by inline-template -->
<div data-vue-component-outlet="v-hello-vue-inline">
  <hello-vue-inline
  :obj="<%= @hello_message[:obj].to_json %>"
  :arr="<%= @hello_message[:arr] %>"
  :str="<%= @hello_message[:str] %>"
  :num="<%= @hello_message[:num] %>"
  inline-template
  >
    <div>
      <h4>{{ message }}</h4>
      <ul>
        <li>Object: {{ obj }} </li>
        <li>Number: {{ num }} </li>
        <li>Array: {{ arr }} </li>
        <li>String: {{ str }} </li>
      </ul>
    </div>
  </hello-vue-inline>
</div>
<!-- End of Vue Component by inline-template -->

<%= link_to 'New User', new_user_path %>

加入 View Helper

我們也可以寫一個 Rails View Helper 來方便我們使用 Vue 組件:
./app/helpers/ 中建立一支 vue_helper.rb
# ./app/helpers/vue_helper.rb
module VueHelper
  def vue_outlet(html_options = {})
    html_options = html_options.reverse_merge(data: {})
    html_options[:data].tap do |data|
      data[:vue_component_outlet] = "_v" + SecureRandom.hex(5)
    end
    html_tag = html_options[:tag] || :div
    html_options.except!(:tag)
    content_tag(html_tag, '', html_options) do
      yield
    end
  end
end
使用方式如下:
<!--
./app/views/users/index.html.erb
-->

<% @hello_message = {num: 1, str: '2', arr: [1, 2, 3], obj: {name: 'foo', age: 12}} %>

<!-- Import Vue Component by Helper -->
<%= vue_outlet do %>
  <hello-turbolinks
  :obj="<%= @hello_message[:obj].to_json %>"
  :arr="<%= @hello_message[:arr] %>"
  :str="<%= @hello_message[:str] %>"
  :num="<%= @hello_message[:num] %>"
  >
<% end %>
<!-- End of Import Vue Component by Helper -->
如果 tag 不想要使用 div 可以加上 options:
<!--
./app/views/users/index.html.erb
-->

<!-- With <p> -->
<%= vue_outlet tag: 'p' do %>
  <hello-turbolinks
  :obj="<%= @hello_message[:obj].to_json %>"
  :arr="<%= @hello_message[:arr] %>"
  :str="<%= @hello_message[:str] %>"
  :num="<%= @hello_message[:num] %>"
  >
<% end %>
<!-- End of With <p> -->

檔案範例

開發者

參考

2017年6月12日

[Vue] 在 Vue 中使用(ES6 import) Bootstrap 4 和 jQuery

由於 bootstrap4 需要依賴 jquery 和 tether 這兩個套件,因此在 webpack 的環境底下使用 bootstrap4 有一些需要留意的細節才能正常載入使用。
⚠️ 這裡使用 @vue/cli 版本為 4.0.5,不同版本的設定方式可能略有不同,須特別留意。

使用 Vue CLI 安裝 vue

# 安裝 Vue CLI,目前版本為 4.0.5
$ npm install -g @vue/cli

# 使用 Vue CLI 建立專案
$ vue create vue-sandbox
vue-cli

安裝 Bootstrap

# 安裝 Bootstrap,目前版本為 4.3.1
$ npm i bootstrap
Imgur

載入 Bootstrap CSS 檔

可以直接在 main.js 中引入 bootstrap 的 css 檔:
// ./src/main.js
import 'bootstrap/dist/css/bootstrap.css'
Imgur

試試看:使用 Bootstrap 的 Alert 元件

現在,先來試試看是否有成功載入 Bootstrap 的樣式。打開 ./src/components/HelloWorld.vue,在裡面放入 Bootstrap 中的 alerts 元件,像這樣:
<!-- ./src/components/HelloWorld.vue -->
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>

    <!-- 開始:Bootstrap alert -->
    <div class="container">
      <div class="alert alert-warning alert-dismissible fade show" role="alert">
        <strong>Holy guacamole!</strong> You should check in on some of those fields below.
        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
    </div><!-- 結束:Bootstrap alert -->

    <p>
      For a guide and recipes on how to configure / customize this project,<br>
      check out the
      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
    </p>

    <!-- ... -->
  </div>
</template>
使用 npm run serve 就可以把專案執行起來。沒有問題的話,畫面應該會像這樣子,可以看到中間已經套用了 Bootstrap 的 Alert 樣式:
Imgur
但此時若點擊 Alert 組件的關閉按鈕時,該警告並不會消失。這是因為我們還沒載入和 Bootstrap 有關的 JavaScript 檔案。

安裝和 Bootstrap 有關的 JavaScript 檔

如果你只是要載入 Bootstrap 的樣式檔,基本上到上面那步就可以了。
但是如果你有需要使用到 bootstrap 的其他互動功能,那麼就需要在額外載入 jQuery, Popper.js 和 Bootstrap 的 js 檔。
因此,讓我們一併安裝 jQuery 和 Popper,js:
$ npm install --save jquery popper.js

載入 Bootstrap 的 JavaScript 檔

要使用 Bootstrap 的 JS 檔,一樣直接在 ./src/main.js 中載入 bootstrap 就可以了:
// ./src/main.js
import Vue from 'vue'
import App from './App.vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap'      // 在這裡載入 Bootstrap 的 JavaScript 檔

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')
此時當我們點擊 Alert 組件的關閉按鈕時,該警告就會消失:
Imgur

載入 jQuery 使用

在上面的例子中,只要載入 Bootstrap 的 JavaScript 檔案後,它會自動去找到相依的 jQuery 套件,因此並不需要額外載入 jQuery 就可以使用。
但有些時候,Bootstrap 的有些互動行為是需要先透過 jQuery 來初始化的,例如 Tooltip 組件。Tooltip 組件在使用前需要針對想要產生 Tooltip 的元素使用 jQuery 來初始化它:
$(function () {
  $('[data-toggle="tooltip"]').tooltip()
})
這時候我們就會需要使用到 jQuery 提供的 $。要怎麼在 Vue 專案中取用到 jQuery 的 $ 呢?這時候我們會需要對 Vue 或者說是 Webpack 進行一些設定。

透過 vue.config.js 設定 webpack

在 Vue 專案中要進行 webpack 的設定,需要在根目錄中新增一支名為 vue.config.js 的檔案(放在和 package.json 同一層):
// 新增一隻名為 vue.config.js 的檔案在專案的根目錄

const webpack = require('webpack');

module.exports = {
  configureWebpack: {
    plugins: [
      new webpack.ProvidePlugin({
        $: 'jquery',
        jQuery: 'jquery',
        'windows.jQuery': 'jquery',
      }),
    ],
  },
};
設定好了之後,在 Vue 專案中,就可以在需要使用 jQuery 的地方匯入 $ 就可以了:
import $ from 'jquery';

試試看:使用 Bootstrap 的 Tooltip 元件

現在讓我們用 Bootstrap Tooltip 元件來測試一下。先在 ./src/components/HelloWord.vue 中加入 Bootstrap 的 Tooltip 元件:
<!-- ./src/components/HelloWorld.vue -->
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>

    <div class="container">
      <!-- Bootstrap Alert -->
      <div class="alert alert-warning alert-dismissible fade show" role="alert">
        <strong>Holy guacamole!</strong> You should check in on some of those fields below.
        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div> <!-- /Bootstrap Alert -->

      <!-- Bootstrap Tooltip -->
      <button type="button" class="btn btn-secondary" data-toggle="tooltip" data-placement="top" title="Tooltip on top">
        Tooltip on top
      </button> <!-- /Bootstrap Tooltip -->
    </div>

    <p>
      For a guide and recipes on how to configure / customize this project,<br>
      check out the
      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
    </p>

    <!-- ... -->
  </div>
</template>
這時候畫面會像這樣,但實際上滑鼠移過去並不會有任何效果:
Imgur
要達到滑鼠移過去有效果的話,需要載入 jQuery 並初始化它。因此我們可以在 ./src/components/HelloWorld.vue<script></script> 內去載入 jQuery 並組件 mounted 之後初始化它,像是這樣:
<!-- ./src/components/HelloWorld.vue -->
<template>
  <!-- ... -->
</template>

<script>
import $ from "jquery";    // STEP 1:載入 jQuery
export default {
  name: "HelloWorld",
  props: {
    msg: String
  },
  mounted() {
    // STEP 2:在 mounted 時初始化 tooltip
    $(function() {
      $('[data-toggle="tooltip"]').tooltip();
    });
  }
};
</script>
完成後,當滑鼠移過去時,就會出現 Tooltip 的提示文字:
Imgur
如此就可以繼續開心的使用 Bootstrap 啦!

完整程式碼

完整程式碼可在 vue-import-bootstrap4 @ github 檢視。

額外補充(將 jQuery 載入到全域環境)

如果我們只是使用 import 'jquery' 這種作法,是無法在全域環境(window)下使用 jQuery(這裡抓到的 $ 是 chrome 中內建的選擇器):
img
因此如果我們希望在全域環境下也可以使用 jQuery,我們可以使用下面這樣的寫法:
// ./src/main.js
import Vue from 'vue'
import App from './App.vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap'

// 讓瀏覽器的全域環境可以使用到 $
import jQuery from 'jquery'
window.$ = window.jQuery = jQuery
img

參考資料

2017年5月14日

[掘竅] 為什麼畫面沒有隨資料更新 - Vue 響應式原理(Reactivity)

之前在在一開始接觸框架的時候,不是很清楚響應式原理到底是什麼,一直會把它和 responsive 這個東西搞混,因為兩個在中文翻起來都是響應式,官網雖然有提及這個部分的說明,但小時候不懂事就給它忽略過去了。
最近在用 Vue 做一些東西的時候,才慢慢瞭解到 Reactivity 的意思和重要性,在使用 Vue 的過程中,我們會發現當我們修改 JavaScript 的資料時,畫面就會自動的更新產生變化,之所以能這麼方便操作,都是由於 Vue 背後的響應式系統(reactivity system),可見 Reactivity 在 Vue 中有多重要!!
也因此,錯誤的使用可能會導致 Vue 的 data 不會有即時更新的效果。
在 Vue 當中,每個組件實例都有相對應的 watcher 實例物件 ,它會紀錄組件渲染過程中所有被觸碰到的屬性,當這些屬性的 setter 被觸發時,就會通知 watcher,進而促使組件重新渲染。
然而,有些情況下你可能會發現即使已經為 JavaScript 中的資料重新設值時,畫面卻沒有重新渲染,這有可能是受限於 JavaScript 物件底層的一些限制,讓我們來看看什麼情況下會導致資料更新了,畫面卻沒有重新渲染:

物件部分:一開始沒有被註冊到的物件不會響應式更新

這是一開始在使用 Vue 或搭配 AJAX 取得資料時很容易疏忽的部分。假設我們現在要做一個計數器,分別記錄不同動物的數量,當我點擊按鈕的時候,該動物的數量就會增加,template 的部分像這樣:
<!-- template -->
<button @click="addCount('dog')">addDogs</button>
<button @click="addCount('cat')">addCats</button>
<button @click="addCount('penguin')">addPenguin</button>
<p>dog {{ counter.dog }}, cat {{ counter.cat }}, penguin {{ counter.penguin }}</p>
在一開始的時,我們在 datacounter 屬性中註冊了 counter.dogcounter.cat 這兩個屬性,但是並沒有把 penguin 給註冊進去,我們可能想說等在 created Hook 中再把資料送進去(通常因為是要透過 AJAX 取的資料內容),順便建立 penguin 屬性就好,因此這時候 JavaScript 的部分像這樣:
new Vue({
  el: '#unregister-object',
  data: {
    // 一開始沒有註冊 penguin,只註冊了 dog 和 cat
    counter: {
      dog: 0,
      cat: 0
    }
  },
  methods: {
    addCount(name) {
      this.counter[name] ++
    }
  },
  created() {
    // 在 created 的時候才建立 penguin 順便設值為 0
    this.counter.penguin = 0
  },
  updated() {
    // 讓我們可以知道組件有被更新
    console.log('view updated')
  }
})
可以點這裡操作範例 @ Codepen
這時候你會發現一個現象,從 console 中我們可以看到,當你按了 addDogsaddCats 時,這個組件會立即的更新。可是當你按 addPenguin 時,畫面卻毫無反應,也不會更新。你可能會以為資料沒有被設定進去,可是如果你又回去點addDogsaddCats 讓畫面更新時,你會發現其實 penguin 的資料有被設定進去,只是沒有產生響應式的變化重新渲染畫面:
所以碰到這樣的問題,我們可以怎麼做呢?

方法一:在 data 中要把所有需要響應式的資料設定進去

如果你希望你的資料能夠在變更時讓畫面重新渲染,達到響應式的效果(Reactive),那麼最簡單的方式是在一開始的 data 中就把資料設定進去,在這裡我們就只要把 penguin 加到 data 中就可以,像是這樣:
data: {
  counter: {
    dog: 0,
    cat: 0,
    penguin: 0       // 一開始就把要響應式變化的資料設定進來
  }
}

方法二:動態添加新的響應式屬性

除了上面的方式,我們也可以透過 vm.$set(object, key, value) 動態新增響應式的屬性,那麼我們只需要在原本的 created(){...} 中改成:
// 也可以使用 Vue.set(object, key, value)
created() {
  // 在 created 的時候才建立 penguin 順便設值為 0
  // 使用 $set 動態新增響應式組件
  this.$set(this.counter, 'penguin', 0)
}
如此當 penguin 被點擊時,一樣會更新組件,重新渲染頁面。

方法三:建立新的物件

另一種當我們要一次更改或新增較多的屬性時,可以透過 Object.assign() 的方法重新建立一個新的物件讓 Vue 去監控,因此,在 created 中可以寫成:
created () {
  // 透過建立新的物件來新增物件的屬性
  this.counter = Object.assign({}, this.counter, {
    penguin: 0,
    dog: 5
  })
}

陣列部分:利用陣列索引直接設值時

剛剛提到的主要是物件的部分,陣列同樣的也會在某些情況下沒有辦法產生響應式的變化,因此如果我們希望畫面會隨著資料能夠有響應式的變化時,應該特別留意和避免。
Template 如下,主要是列出一個動物清單,當我按下 Change Animal 時,它會根據我所輸入的內容以陣列索引的方式變更陣列中的資料內容。另外我們多寫了一個 forceUpdate 的按鈕,點下去之後可以強制更新 vue 組件。
<!-- template -->
<div id="change-by-array-index" v-cloak="v-cloak">
  <h3>利用索引值變更 index 0 的值</h3>
  <input type="text" v-model="animal"/>
  <button v-on:click="changeAnimal">Change Animal</button>
  <ul>
    <li v-for="item in animals" v-bind:key="item">{{ item }}</li>
  </ul>
  <button v-on:click="$forceUpdate()">forceUpdate</button>
</div>
JS 的部分有 changeAnimal 這個 function 會把使用者填寫的資料以陣列索引的方式代入 animals[0]
new Vue({
  el: '#change-by-array-index',
  data: {
    animal: '',
    animals: ['<YOURAnimal>', 'cat', 'penguin', 'bird', 'rabbit']
  },
  methods: {
    changeAnimal () {
      // 當利用陣列索引直接設置值時
      this.animals[0] = this.animal
    }
  }
})
可以點這裡操作範例 @ Codepen
這時候會碰到跟剛剛物件時一樣的問題,就是當我填完 input 內容,按下 changeAnimal 的按鍵時,畫面沒有任何反應,這是因為 以陣列索引值的方式修改資料內容時,Vue 無法監測到 ,因此雖然資料已經代進去了,但是 Vue 不會更新。
和剛剛類似,你可以透過按下 forceUpdate 這個按鈕來讓 Vue 強制更新,強制更新後你就會發現,畫面更新成你剛剛填入的資料內容,表示其實剛剛是有把資料設定到 Vue 當中,只是 Vue 沒有監測到,因此沒有觸發組件去做 update

方法一:使用 Vue 可觀察到的陣列方法

在 Vue 中包含一組可以觀察陣列的方法,而這些方法將能促使畫面重新渲染。這些方法包含:push()pop()shift()unshift()splice()sort()reverse()
因此,回到剛剛的例子上,我們可以使用 arr.splice(startIndex, deleteCount, addItem) 這個方法來把陣列中的內容抽換掉,因此我們可以把 methods 改成:
methods: {
  changeAnimal () {
    // 利用 arr.splice(startIndex, deleteCount, addItem)
    this.animals.splice(0, 1, this.animal)
  }
}

方法二:使用 vm.$set

和物件類似,我們也可以使用 vm.$set(array, index, value) 來達到響應式修改資料內容,我們可以把 methods 改成:
methods: {
  changeAnimal () {
  // 利用 vm.$set(array, index, value) 方法
    this.$set(this.animals, 0, this.animal)
  }
}
如此一樣可以達到響應式變換陣列的資料內容。

瞭解 Vue Reactivity 的原理

See the Pen [Demo] Vue Reactivity in Vanilla JavaScript by PJCHEN (@PJCHENder) on CodePen.

總結

當你對於 Vue 的響應式原理(Reactivity System)有了更多的瞭解後,相信你可以少踩到一些莫名其妙或不必要的坑。
操作範例:

參考資料