Vue 入門指南

一倍速的夏味發表於2018-07-11

Vue 入門指南

圖片來源:pixiv 63737968

學習 vue 過程中的筆記,未完更新中 ... 完整【示例程式碼】請去我的 GitHub 倉庫 pero-vue 檢視

更新於 2017.12.24
首發於夏味的部落格: xiaweiss.com

1. 環境配置

注意本筆記使用的版本為當時的最新穩定版

  • Vue 2.x
  • webpack 2
  • node 8.9.0
  • npm 5.6.0

1.1 使用到的技術文件

1.2 需要安裝的相關依賴,未來不一定正確,以官方文件為準

首先需要安裝 node, 然後使用命令 npm install 依賴名稱 來安裝

  • babel-core
  • babel-loader
  • babel-preset-env
  • babel-preset-stage-2 (使用 import() 時才需要)
  • css-loader
  • html-webpack-plugin
  • style-loader
  • vue
  • vue-loader
  • vue-template-compiler
  • webpack
  • webpack-dev-server
  • vue-router
  • axios
  • vuex(選用)

1.3 webpack 配置項簡介

專案根目錄下,建立 webpack.config.js 配置檔案

const path = require('path'); //node 內建模組
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 用於在 html 頁面裡自動插入資源引用的標籤
const webpack = require('webpack'); //使用 webpack 內建的外掛時,需要引入 webpack

module.exports = {
  entry: {
    index: './src/index.js' // 入口檔案
    // bbb: './src/bbb.js' // 可以多個入口檔案
  },
  output: {
    path: path.resolve('./dist'), // 輸出路徑,必須是絕對路徑.path.resolve是nodeJS模組方法,把相對路徑轉為絕對路徑
    // 或者使用語法__dirname +'/dist' 或  path.join(__dirname,'dist')
    // __dirname 表示當前模組的目錄的絕對路徑(並非全域性變數,等價於path.dirname(__filename))
    // path.join用於處理連線路徑時,統一不同系統路徑符\和/問題。
    // publicPath: '/assets/', // 釋出路徑,填寫此項後,打包後檔案路徑不再是相對路徑,而是基於伺服器根目錄的路徑,
    filename: 'js/[name].js', // [name] 表示塊的名稱,輸出檔名,可以包含路徑
    chunkFilename: 'js/[name].js' //webpack 分割後的檔案,[id] 表示塊的編號。[name] 表示塊的名稱,沒有名稱時會自動使用編號
  },
  resolve: {
    alias: {
      vue$: 'vue/dist/vue.esm.js' // 預設是執行時構建,這裡使用了template,必須用執行+編譯時構建
    }
  },
  module: {
    rules: [
      { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }, //使用 babel 對 js 轉譯
      {
        test: /\.vue$/,
        exclude: /node_modules/,
        loader: 'babel-loader!vue-loader'
      },
      // 先使用 vue-loader對 vue 檔案轉譯
      { test: /\.css$/, loader: 'style-loader!css-loader' }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html', // 輸出檔名,可以包含路徑
      template: 'src/index.html', // 模板檔案位置
      inject: 'body', //插入位置,也可以寫 head
      hash: true, // 在檔名後面加 hash 值,預設false
      chunks: ['index'] // 表示插入的塊,對應 webpack 入口檔案中的 index,不寫會插入所有的入口檔案
    }),
    new webpack.HotModuleReplacementPlugin() //如果 devServer 的配置項裡 hot:true ,則需要配置此項
  ],
  externals: {
    vue: 'Vue' //打包時排除 vue,vue內容不會被寫入js。
    //注意左邊的 vue 是模組名,右邊的 Vue 是不使用構建工具時的標準變數名,必須是Vue,與import的變數名無關
  },
  devServer: {
    contentBase: './dist/', //表示靜態資源(非webpack編譯產生)檔案的目錄位置,
    //這個目錄的資源會被放到同樣當成伺服器根目錄去
    //遇到同名檔案,webpack編譯後產生的檔案優先順序更高
    compress: true, //是否壓縮
    port: 9000, //埠號
    host: '0.0.0.0', //預設是localhost,如果想被外部訪問,這樣設定
    historyApiFallback: true, //當使用 history 模式路由時,設定為true,404頁面會被重定向到主頁,
    hot: true // 熱替換,可以在不重新整理頁面的情況下更新修改後資料,也可以配置在package.json 的 scripts 裡,加 --hot引數
  }
};
複製程式碼

專案根目錄下,建立 .babelrc 配置檔案

babel-preset-env 相當於 es2015 ,es2016 ,es2017 及最新版本

{
  "presets": ["env"]
}
複製程式碼

1.4 執行

package.json 檔案裡新增配置

{
  // ...其他引數
  "scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server"
  }
}
複製程式碼

然後使用 npm run dev 來啟動 server 使用 npm run build 來打包輸出

這裡的 build 是自己起的。寫為 "build": "webpack -p", 打包後時壓縮程式碼

1.5 webpack-dev-server 熱替換

熱替換指,在不重新整理頁面的狀態下,把修改後的結果更新到頁面上

有兩種配置方式

  • webpack CLI 方式:

package.json 檔案裡新增配置

{
  "scripts": {
    "dev": "webpack-dev-server --hot"
  }
}
複製程式碼
  • webpack 配置方式:

webpack.config.js 檔案裡新增配置

const webpack = require('webpack');
module.exports = {
  // 其他配置...
  plugins: [
    // 其他配置...
    new webpack.HotModuleReplacementPlugin()
  ],
  devServer: {
    // 其他配置...
    hot: true
  }
};
複製程式碼

package.json 檔案裡依然是 "dev": "webpack-dev-server"

2. vue 語法

2.1 基本用法

<div id="app">
  {{ name }}
</div>
複製程式碼
import Vue from 'vue';

let param = {
  el: '#app',
  data: {
    name: 'hello vue'
  }
  //注意這裡的 data 也可以使用這種語法
  //data() {
  //   return {name:'hello vue'}
  //}
};
new Vue(param);
複製程式碼

2.2 基本的元件

<div class="container">
  <my-name></my-name>
</div>
複製程式碼
import Vue from 'vue';
// 這是一個元件
let meName = {
  template: '<div>{{name}}</div>', // 元件的模板,渲染後會替換掉 <my-name></my-name>
  data() {
    return {
      name: 'xiawei'
    }; // 元件中 data 必須是函式,資料 return 出去,不可以寫為 data:{name:'xiawei'}
  }
};

new Vue({
  el: '.container',
  components: {
    'my-name': meName
  }
});
複製程式碼

2.3 vue 檔案形式的元件

為了方便,可以使用 vue 檔案 來封裝元件,可以認為一個 vue 檔案是一個元件,子元件繼續使用其他 vue 檔案 引入。

index.js

import Vue from 'vue';
import myname from './components/myname.vue';

new Vue({
  el: '.container',
  components: {
    'my-name': myname
  }
});
複製程式碼

myname.vue

<template>
  <div>
    {{name}}
  </div>
</template>
<script>
export default {
  data() {
    return {
      name: 'xiawei'
    };
  }
};
</script>
<style lang="css" scoped>
</style>
複製程式碼

template 裡,必須用一個 div 或者一個其他標籤,包裹住所有的 html 標籤

預設 lang="css" 可以省略,需要使用 sass 時,可以寫 lang="scss" 等

scoped 是 vue 提供的屬性 表示這裡的樣式只能在本元件內生效

2.4 元件通訊

2.4.1 使用 props 給元件傳參

<myname value="xiawei"></myname>
複製程式碼
<template>
  <div>
    {{value}}
  </div>
</template>
<script>
export default {
  props:['value']
};
</script>
複製程式碼

2.4.2 訪問其他元件,獲取引數

可以通過 $parent 訪問父元件,$children 訪問子元件

user-login 有三個子元件,部分程式碼如下

<template>
  <div id="user">
        <h2>User Login</h2>
        <form>
            <user-name></user-name>
            <user-pass></user-pass>
            <user-submit></user-submit>
        </form>
    </div>
</template>
複製程式碼

這時在 user-submit 元件

<template>
    <div>
      <button v-on:click="test">submit</button>
      <!-- v-on:click="test" 表示點選事件時,觸發 test 函式 -->
    </div>
</template>
<script>
export default {
  methods: {
    test() {
      this.$parent.$children[0] //訪問 user-name 元件
      this.$parent.$children[1] //訪問 user-pass 元件

      //獲取 user-name 元件 data 中 username 的值
      this.$parent.$children[0].username
    }
  }
};
</script>
複製程式碼

要區分子元件是第幾個,並不方便,可以使用 ref 來解決這個問題

相關程式碼修改為以下即可

<user-name ref="uname"></user-name>
<user-pass ref="upass"></user-pass>
複製程式碼
//獲取 user-name 元件 data 中 username 的值
this.$parent.$refs.uname.username;
複製程式碼

2.4.3 父子元件自定義事件通訊

父元件 user-login.vue 裡,給子元件 user-name 設定自定義事件 updateUserName

這個事件是繫結在 user-name 元件上的,在 元件物件 .$listeners裡可以檢視到,可以用 元件物件 .$emit 來觸發

$emit 觸發時,引數 1 是事件名,後幾個引數可以傳給事件物件(類似 jQuery 的trigger 方法)

<template>
  <user-name ref="uname" v-on:updateUserName="setUserName"></user-name>
</template>
<script>
import username from './user/user-name.vue';

export default {
  data() {
    return {
      username: ''
    };
  },
  components: {
    'user-name': username
  },
  methods: {
    setUserName(uname) {
      this.username = uname;
    }
  }
};
</script>
複製程式碼

子元件 user-name.vue,當輸入框內容改變,觸發 change 事件

然後執行了 $emit 來觸發 updateUserName事件,this.username 作為引數傳給了updateUserName 事件

<template>
  <input type="text" v-model="username" v-on:change="userNameChange">
</template>
<script>
export default {
  data() {
    return {
      username: ''
    };
  },
  methods: {
    userNameChange() {
      this.$emit('updateUserName', this.username);
    }
  }
};
</script>
複製程式碼

2.5 v-if,路由原理

v-if 主要用於渲染模板,下面程式碼

當變數 isadmin 為 true 時,只顯示 Admin Login

反之,只顯示User Login

注意,程式依據 isadmin == true 的結果來判斷

<template>
  <h2 v-if="isadmin">Admin Login</h2>
  <h2 v-else>User Login</h2>
</template>
複製程式碼

在 index.js 新增下面程式碼

當瀏覽器路徑 hash 部分(#號及其後面的部分)變化時,會觸發 hashchange 事件

判斷 hash 的值,各種值走自己的業務邏輯,就可以切換頁面、改變資料,這就是路由原理

window.onhashchange = function() {
  if (window.location.hash === '#admin') {
    myvue.$children[0].$data.isadmin = true;
  } else {
    myvue.$children[0].$data.isadmin = false;
  }
};
複製程式碼

相關需要掌握的還有 v-for,參見官方文件

2.6 計算屬性 computed

計算屬性和 data 裡的普通屬性呼叫時相同的,但定義時不同

計算屬性使用函式定義,return 的值,就是計算屬性的值

當計算屬性內的其他變數的值發生變化時,函式就會執行,運算得到新的值

所以計算屬性的值是依賴其他變數的,它沒有初始值,不可以在 data 裡宣告

下面的例子,通過計算屬性比對輸入的值來篩選 fav.class2

filter 陣列方法 返回通過篩選條件的新陣列,當 return true 時符合條件被選入。

indexOf 字串方法 返回符合條件的字串序號,如果找不到時,會返回數字 -1,可以用來匹配字串類似的方法,還有 indexOf 陣列方法

<template>
  <input type="text" v-model="inputText" class="form-control">
  <table v-if="isShow()" class="table">
    <thead>
      <tr>
        <th>
          type 1
        </th>
        <th>
          type 2
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="fav in getFavs">
        <td>{{ fav.class1 }}</td>
        <td>{{ fav.class2 }}</td>
      </tr>
    </tbody>
  </table>
</template>
<script>
export default {
  data() {
    return {
      favs: [
        { class1: 'web', class2: 'js' },
        { class1: 'pro', class2: 'java' }
      ],
      inputText: ''
    };
  },
  methods: {
    isShow() {
      return !(this.inputText == '');
    }
  },
  computed: {
    getFavs() {
      return this.favs.filter(abc => {
        return abc.class2.indexOf(this.inputText) >= 0;
      });
    }
  }
};
</script>
複製程式碼

2.6.1 計算屬性配合過濾方法

vue 2.x 的過濾器方法,與 vue 1.x 語法不同,並不適合和 v-for 配合使用,計算屬性配合過濾方法來實現。

上節的例子,更復雜一點,陣列的情況 ( 和上面重複的部分沒寫出來,完整程式碼請檢視github)

getFavs 決定展示第幾條資料,filterClass2 負責對展示出來的資料篩選

<template>
  <tr v-for="fav in getFavs">
    <td>{{ fav.class1 }}</td>
    <td><a v-for="code in filterClass2(fav.class2)">{{ code }} </a></td>
  </tr>
</template>
<script>
export default {
  data() {
    return {
      favs: [
        { class1: 'web', class2: ['js', 'html', 'css', 'jssdk'] },
        { class1: 'pro', class2: ['java'] }
      ]
    };
  },
  methods: {
    filterClass2(class2) {
      return class2.filter(v => {
        return v.indexOf(this.inputText) >= 0;
      });
    }
  },
  computed: {
    getFavs() {
      return this.favs.filter(abc => {
        return abc.class2.filter(code => {
          return code.indexOf(this.inputText) >= 0;
        }).length > 0;
      });
    }
  }
};
</script>
複製程式碼

2.7 路由

2.7.1 路由的基本使用

首先 npm 安裝依賴官方的路由外掛 vue-router

index.html

  <div class="container">
    <page-nav></page-nav>
    <router-view></router-view>
  </div>
複製程式碼

index.js 檔案

import Vue from 'vue';
import VueRouter from 'vue-router'; // 引入外掛
Vue.use(VueRouter); // 使用外掛

import pagenav from './components/page-nav.vue';
import newslist from './components/news-list.vue';
import userlogin from './components/user-login.vue';

const routerConfig = new VueRouter({
  routes: [
    { path: '/news', component: newslist },
    { path: '/login', component: userlogin }
  ]
});

// 全域性註冊公共元件(也可以像原先註冊子元件的方式來做)
Vue.component('page-nav', pagenav);

let myvue = new Vue({
  el: '.container',
  router: routerConfig
  // 路由中引入過子元件了,所以此處不需要再引入子元件
  // components: {
  //   'page-nav': pagenav,
  //   'user-login': userlogin
  // }
});
複製程式碼

page-nav.vue 的部分程式碼

推薦使用 router-link 語法作為切換按鈕,它預設會渲染成 a 標籤也可以使用 a 標籤來做

當某個 router-link 被點選選中時,vue 會給它的 html 標籤新增上 class router-link-active

可以通過給 .router-link-active寫 css 樣式, 來給選中的 router-link 新增樣式

<template>
  <ul class="nav navbar-nav">
    <li><router-link to="/login">login</router-link></li>
    <li><a href="#/news">News</a></li>
  </ul>
</template>
複製程式碼

2.7.2 axios 的基本使用

引入

import axios from 'axios';
複製程式碼

如需全域性引入,可以再加上下面這句,元件內呼叫時使用 this.$axios 即可

Vue.prototype.$axios = axios;
複製程式碼

get 請求

axios
  .get('http://localhost:8000/test.php', {
    params: {
      ID: 12345
    }
  })
  .then(response => {
    alert(response.data);
  });
複製程式碼

post 請求引數 axios 預設轉為 json 格式

axios
  .post('http://localhost:8000/test.php', { name: 'xiawei', age: 20 })
  .then(response => {
    alert(response.data);
  });
複製程式碼

鍵值對方式(php 用 $_POST 可以取到值)

axios.post('http://localhost:8000/test.php', 'name=xiawei&age=20');
複製程式碼

也可以使用 node 內建模組來轉換格式

import querystring from 'querystring';
axios.post(
  'http://localhost:8000/test.php',
  querystring.stringifyname({ name: 'xiawei', age: 20 })
);
複製程式碼

這部分的 php 程式碼,是放置在專案根目錄的 test.php 檔案

<?php
  //指定允許其他域名訪問
  header('Access-Control-Allow-Origin:*');

  //響應型別
  header('Access-Control-Allow-Methods:GET,POST,PUT');
  header('Access-Control-Allow-Headers:x-requested-with,content-type');

  echo file_get_contents('php://input');//顯示接收到的原始資料
  var_export($_POST);
  echo 'hello php';
複製程式碼

Mac 內建了 php,直接啟動 php 內建服務:到專案根目錄下,Terminal 裡執行下面命令即可

windows 下載 php 後,把 php 目錄新增到系統環境變數 PATH 裡後,同樣執行下面命令

php -S 0.0.0.0:8000
複製程式碼

2.7.3 動態載入新聞詳細頁

在新聞列表頁,點選標題跳轉到新聞詳細頁,動態載入新聞內容

index.js 部分程式碼

import axios from 'axios';
Vue.prototype.$axios = axios;

const routerConfig = new VueRouter({
  routes: [
    { path: '/', component: newslist },// 設定首頁
    { path: '/news', component: newslist, name: 'newslist' },// 可以給路由設定別名 name
    { path: '/news/:newsid', component: newsdetail, name: 'newsdetail' },// 如果需要引數,使用冒號的來做佔位符
    { path: '/login', component: userlogin, name: 'userlogin' }
  ]
});
複製程式碼

new-list.vue 部分程式碼

<template>
  <div class="page-header" v-for="news in newslist">
    <h2><router-link :to="{ name: 'newsdetail', params: {newsid: news.newsid} }">{{news.title}}</router-link> <small>{{news.pubtime}}</small></h2>
    <!-- 這裡的 newsdetail 以及 params 裡左邊的 newsid 是和路由定義時的相關引數對應 -->
    <p>{{news.desc}}</p>
  </div>
</template>
複製程式碼

news-detail.vue 部分程式碼

<template>
  <div>
    <h2> {{ newstTitle }} <small>{{ newsDate }}</small></h2>
    <p>{{ newsContent }}</p>
  </div>
</template>
<style>

</style>
<script>
export default {
  // 生命週期,元件被建立時執行
  created() {
    this.$axios
      .get('http://localhost:8000/news.php?newsid='+ this.$route.params.newsid)
      // 可以通過全域性變數 $route.params 來訪問路由裡的變數獲取到新聞編號101
      .then(response => {
        this.newstTitle = response.data.title;
        this.newsDate = response.data.pubtime;
        this.newsContent = response.data.desc;
      });
  }
};
</script>
複製程式碼

通過全域性變數 $route 來訪問路由裡的各種資料

例如 $route.params.newsid 可以獲得路由佔位符 :newsid 處的新聞編號值 101

2.8 非同步載入和 webpack 程式碼分割

當專案比較大的時候,可以使用非同步載入元件的方式來按需載入,而不是一次性載入全部元件。

還可以配合 webpack 程式碼分割功能,把打包後的 js,分割成多個 js 檔案,做到按需引用。

之前的引入元件的方式是

import userlogin from './components/user-login.vue';
複製程式碼

使用 vue 非同步載入的方式引入

var userlogin = function(resolve) {
  resolve(require('./components/user-login.vue'));
};
複製程式碼

使用 ES2015 語法,並且簡化引數名,可以寫為

const userlogin = r => {
  r(require('./components/user-login.vue'));
};
複製程式碼

結合 webpack 程式碼分割功能後

const userlogin = r => {
  require.ensure([], () => {
    r(require('./components/user-login.vue'));
  });
};
複製程式碼

如果需要把某幾個元件打包為一組,給它們的 require.ensure() (文件1文件2)新增最後一個引數(例如'aaa'),且值相同

  require.ensure([], () => {
    r(require('./components/user-login.vue'));
  },'aaa');
複製程式碼

也可以使用 webpack + ES2015 語法來進行程式碼分割

import() (文件) 是 ES2015 草案的語法,所以使用時需要 babel 轉譯

babel 配置裡需要新增草案語法的轉譯 presets stage-2 ,npm 安裝依賴 babel-preset-stage-2

.babel 檔案,注意配置的陣列裡,presets 解析的順序是從右到左的,先執行 stage-2

{
  "presets": ["env", "stage-2"]
}
複製程式碼
const userlogin = () => import('./components/user-login.vue');
// 也就是 function() { return import('./components/user-login.vue')};
複製程式碼

把某幾個檔案打包為一組時,使用這個語法

const userlogin = () => import(/* webpackChunkName: "aaa" */'./components/user-login.vue');
複製程式碼

最後分割後的檔名,可以在 webpack 配置裡 output 配置項裡新增 chunkFilename 配置項來控制

output: {
  filename: 'js/[name].js', // [name] 表示塊的名稱,輸出檔名,可以包含路徑
  chunkFilename: 'js/[name].js'
  //webpack 分割後的檔案,[id] 表示塊的編號。[name] 表示塊的名稱,沒有名稱時會自動使用編號
},
複製程式碼

2.9 開發外掛

有時現有的外掛並不能滿足自己的業務需求,這時需要自己開發外掛

2.9.1 自定義指令

在 src 資料夾下新建一個 js 檔案,比如命名為 plugin.js

export default {
  install(Vue) {
    // 新增例項方法
    Vue.prototype.$name = 'xiawei';// 可以在元件內使用 this.$name 取到值 'xiawei'

    // 這裡新增時方法來檢測使用者名稱是否合法,6~20位合法,否則顯示提示
    Vue.prototype.checkUserName = value => {
      if (value == '') return true;
      return /\w{6,20}/.test(value);
    };
    // 可以在元件內使用 this.checkUserName(’‘’)

    // 新增全域性自定義指令 v-uname
    Vue.directive('uname', {
      bind() {
        console.log('begin');
      },
      update(el, binding, vnode) {
        vnode.context[binding.expression] = !/\w{6,20}/.test(el.value);
      }
    });
  }
};
複製程式碼

directive (文件) 裡的生命週期裡的三個引數:

  • el 參數列示指令所繫結的元素,可以用來直接操作 dom
  • binding 參數列示繫結物件,binding.expression 取到傳入的表示式,binding.value 可以取到表示式的值 這裡的表示式也可以是函式名,取到的值是函式體,binding.oldValue
  • vnode 參數列示 Vue 編譯生成的虛擬節點

關於官方文件裡,新增全域性方法或屬性 Vue.myGlobalMethod 和新增例項方法和屬性 Vue.prototype.$myMethod 二者區別

全域性方法或屬性使用 Vue.名稱 來呼叫,而例項方法和屬性使用 (例項化後的 Vue 物件).名稱 來呼叫,也就是元件內的常見 this.名稱 來呼叫,即使看起來名稱一樣的Vue.aaaVue.prototype.aaa也是兩個不同的變數

具體可以參見這篇文章:js裡面的例項方法和靜態方法

index.js 內載入外掛

import plugin from './plugin.js';
Vue.use(plugin);
複製程式碼

user-name.vue 新增 v-uname 和 label 元素

<input type="text" v-model="username" v-uname="showErrorLabel" v-on:change="userNameChange" class="form-control" :placeholder="placeholder">
<label v-if="showErrorLabel" class="label label-danger">Please check your username and try again</label>
複製程式碼

2.9.2 手動掛載子元件

上面只是控制變數,並不是很方便,可以通過外掛動態插入移除提示框

export default {
  install(Vue) {
    // 建立變數,定義初始值
    Vue.errorLabel = null;
    Vue.hasErrorLabel = false;
    // 這個全域性變數來標記是否插入了 label,給初始值時必須放在 update 外面

    // 新增全域性自定義指令 v-uname
    Vue.directive('uname', {
      bind(el) {
        let error = Vue.extend({
          template:
            '<label class="label label-danger">Please check your username and try again</label>'
        });

        Vue.errorLabel = (new error()).$mount().$el;
        // $mount() 方法不填引數時,表示把 vm 例項物件變成一個可以掛載的狀態,這時就可以訪問到 $el 獲取到元素了
      },
      update(el, binding, vnode) {
        // 這裡每次 update 是從組建原始的狀態 update 的,所以不會重複插入多個
        if (/\w{6,20}/.test(el.value)) {
          if (Vue.hasErrorLabel) {
            el.parentNode.removeChild(Vue.errorLabel);
            Vue.hasErrorLabel = !Vue.hasErrorLabel;
          }
        } else {
          if (!Vue.hasErrorLabel) {
            el.parentNode.appendChild(Vue.errorLabel);
            Vue.hasErrorLabel = !Vue.hasErrorLabel;
          }
        }
      }
    });
  }
};
複製程式碼

user-name.vue 元件裡,這時不需要寫 label 元素,只需要寫入 v-uname 即可

<input type="text" v-model="username" v-uname v-on:change="userNameChange" class="form-control" :placeholder="placeholder">
複製程式碼

2.9.3 外掛裡包含子元件

上一小節的程式碼,當有多個 input 元素時,就會出現其他元素顯示不正常的情況,原因是多個標籤共用了同一個 Vue.hasErrorLabel

所以當外掛不僅僅處理資料時,還需要獨立的處理 dom 元素時,使用子元件的方式更加合理,它們是互相獨立的

export default {
  install(Vue) {
    Vue.component('p-username', {
      template: `<div>
          <input class="form-control" type="text" v-model="textValue" />
          <label class="label label-danger" v-if="showErrorLabel">Please check your username and try again</label>
        </div>`,
        // 這裡使用了 ES2015 的模板字串語法
      data() {
        return {
          textValue: ''
        };
      },
      computed: {
        showErrorLabel() {
          return !(/\w{6,20}/.test(this.textValue) || this.textValue == '');
        }
      }
    });
  }
};
複製程式碼

其中,為了方便 template 裡使用了 ES2015 的模板字串語法(參考文件

user-name.vue 檔案(不需要寫 input 元素)

<p-username></p-username>
複製程式碼

2.10 全域性狀態管理 vuex

應遵循以下規則

  • 應用級的狀態集中放在 store 中
  • 計算屬性使用 getters
  • 改變狀態的方式是提交 mutations,這是個同步的事務
  • 非同步邏輯應該封裝在 action 中

也即是與元件的概念相對應的 store -> data getters -> computed mutations/actions -> methods

2.10.1 vuex 基本使用

npm 安裝依賴 vuex

index.js

import Vuex from 'vuex';
Vue.use(Vuex);

const vuex_store = new Vuex.Store({
  state: {
    user_name: ''
  },
  mutations: {
    showUserName(state) {
      alert(state.user_name);
    }
  }
});
複製程式碼

賦值:user-name.vue 元件中使用

<div class="page-header" v-for="news in $store.state.newslist">
複製程式碼
this.$store.state.user_name = this.username;
複製程式碼

觸發:user-submit.vue 元件中使用

this.$store.commit('showUserName');
複製程式碼

即可完成簡單的輸入使用者名稱,點提交按鈕後 alert 出使用者名稱

2.10.2 vuex 計算屬性

vuex 裡的計算屬性使用的是 getters,用法和 元件裡的計算屬性 computed 類似,只是被觸發的時機不同

從資料裡展示沒有刪除的新聞展示

index.js

const vuex_store = new Vuex.Store({
  state: {
    user_name: '',
    newslist: []
  },
  mutations: {
    showUserName(state) {
      alert(state.user_name);
    }
  },
  getters: {
    getNews(state) {
      return state.newslist.filter(news => !news.isdeleted);
    }
  }
});
複製程式碼

news-list.vue

<div class="page-header" v-for="news in $store.getters.getNews">
複製程式碼
export default {
  created() {
    if (this.$store.state.newslist.length == 0) {
      this.$axios.get('http://localhost:8000/news.php').then(response => {
        this.$store.state.newslist = response.data;
      });
    }
  }
};
複製程式碼

2.10.3 actions

mutations 是同步執行的,裡面不能放非同步執行的東西 actions 裡放非同步執行的,非同步執行完後,去手動觸發 mutations

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context,param) {
      // 非同步業務 param -> param2
      context.commit('increment',param2);
    }
  }
})
複製程式碼

元件內觸發

this.$store.dispatch('increment',param);
複製程式碼

2.10.4 把業務按模組分類

之前寫的 index.js 是這樣

const vuex_store = new Vuex.Store({
  state: {
    user_name: '',
    newslist: []
  },
  mutations: {
    showUserName(state) {
      alert(state.user_name);
    }
  },
  getters: {
    getNews(state) {
      return state.newslist.filter(news => !news.isdeleted);
    }
  }
});
複製程式碼

按模組分離後

index.js

import news_module from './store/news.js';
import users_module from './store/users.js';

const vuex_store = new Vuex.Store({
  modules: {
    news: news_module,
    users: users_module
  }
});
複製程式碼

news.js

export default {
  state: {
    newslist: []
  },
  getters: {
    getNews(state) {
      return state.newslist.filter(news => !news.isdeleted);
    }
  }
}
複製程式碼

users.js

export default {
  state: {
    user_name: ''
  },
  mutations: {
    showUserName(state) {
      alert(state.user_name);
    }
  }
}
複製程式碼

分離後,注意相關模組裡的 this.$store.state

按業務模組名分別改為 this.$store.state.newsthis.$store.state.users

注意不同業務模組裡,getters 裡函式重名了會報錯, mutations 裡函式重名了會兩邊都執行

推薦課程:VUE.JS+PHP 前後端分離實戰視訊電商網站

Vue 入門指南

歡迎新增我個人微信,互相學習交流

相關文章