1. 導(dǎo)語
承接上文:Vue 2.x 實戰(zhàn)之后臺管理系統(tǒng)開發(fā)(一)
在上一篇文章中,我詳細(xì)敘述了如何創(chuàng)建項目框架和引入各種后臺常用插件,做好這些準(zhǔn)備工作后,我們就可以著手進(jìn)行頁面的開發(fā)了。在開發(fā)過程中,會遇到一些常見的需求和問題,我整理了一下,有以下幾點。
2. 常見需求
01. 父子組件通信
a. 父 -> 子(父組件傳遞數(shù)據(jù)給子組件)
使用 props,具體查看文檔 - 使用 Prop 傳遞數(shù)據(jù)(https://cn.vuejs.org/v2/guide/components.html#使用-Prop-傳遞數(shù)據(jù))
b. 父 -> 子(在父組件上調(diào)用子組件內(nèi)的方法)
使用 ref,具體查看文檔 - 子組件索引(https://cn.vuejs.org/v2/guide/components.html#子組件索引)
<!--父組件 template-->
<div id="parent">
<!--子組件-->
<user-profile ref="profile"></user-profile>
</div>
// 父組件 script
this.$refs.profile.someMethod();
注意:如果在子組件上設(shè)置 ref 屬性,則可以通過 this.$refs 獲取到該子組件對象,如果在普通的 html 標(biāo)簽上設(shè)置 ref 屬性,則獲取到的是 Dom 節(jié)點。
c. 子 -> 父(在父組件上獲取子組件內(nèi)的數(shù)據(jù))
同上,也是利用 ref
// 父組件 script
let childData = this.$refs.profile.someData;
d. 子 -> 父(子組件內(nèi)觸發(fā)事件,父組件監(jiān)聽事件)
父組件可以在使用子組件的地方直接用
v-on來監(jiān)聽子組件觸發(fā)的事件,具體查看文檔 - 使用 v-on 綁定自定義事件(https://cn.vuejs.org/v2/guide/components.html#使用-v-on-綁定自定義事件)
<!--父組件 template-->
<div id="parent">
<!--子組件-->
<user-profile @childTrigger="parentHandle"></user-profile>
</div>
// 父組件 script
methods: {
parentHandle(params){
// 這個方法在子組件 emit childTrigger 事件后會執(zhí)行
// params 為子組件里觸發(fā)事件時傳的參數(shù)
}
}
// 子組件 user-profile script
this.$emit('childTrigger', params);
e. 子 -> 父(子組件傳值,父組件里使用,具體實現(xiàn)見 03)
01總結(jié):
應(yīng)用場景示例:在父組件上打開側(cè)邊欄子組件,可以傳 prop visible(true)來控制側(cè)邊欄打開;側(cè)邊欄內(nèi)部有關(guān)閉按鈕,就在點擊關(guān)閉按鈕后觸發(fā)一個事件,父組件監(jiān)聽事件執(zhí)行方法將 data visible 改為false。
PS:父組件傳值到子組件,傳的值是Object類型,子組件使用v-model可以修改該值(將某個表單元素的v-model設(shè)為該值),父組件可直接獲取到改變后的值。
02. 全局函數(shù)
有時候會用到一些工具類函數(shù),希望可以全局調(diào)用,而不是局限于某個組件中。
Step 1:
在 項目根目錄/static/js/ 目錄下新建一個 util.js 文件,將常用的工具函數(shù)寫在這里面。
Step 2:
在 index.html 里面引入 util.js,就可以在 .vue 文件里使用那些方法了,如下:
<body>
<div id="app"></div>
<!-- 引入常用 js 方法 -->
<script type="text/javascript" src="/static/js/util.js"></script>
<!-- built files will be auto injected -->
</body>
02總結(jié):
使用這個方法可以使得一些使用頻率高的函數(shù)可以在所有.vue文件中被調(diào)用,笨拙而又簡單。
03. slot
以前看文檔時一直不理解如何使用 slot,現(xiàn)在用多了 elementui 的組件之后,就漸漸發(fā)現(xiàn)了它的實用性。
簡單來說,使用 slot 可以使我們做到:在父組件里使用子組件時,在子組件里插入一些自定義的內(nèi)容(html 代碼啥的),具體查看文檔:http://cn.vuejs.org/v2/guide/components.html#使用-Slot-分發(fā)內(nèi)容;
更神奇的功能是 作用域插槽,可以讓我們在父組件里使用子組件時,獲取子組件傳來的數(shù)據(jù),具體查看文檔:http://cn.vuejs.org/v2/guide/components.html#作用域插槽。
簡單應(yīng)用示例:
<!-- a-button 子組件 -->
<button type="button" class="a-button" @click="$emit('btn-click')"><slot></slot></button>
<!-- 這里監(jiān)聽了 button 的 click 事件,然后又觸發(fā)了 btn-click 事件 -->
<!-- 父組件 -->
<a-button @btn-click="handleClick">這里寫的東西會覆蓋子組件里的 slot 標(biāo)簽所在的位置</a-button>
<!-- 這里監(jiān)聽了子元素觸發(fā)的 btn-click 事件,然后執(zhí)行 handleClick 函數(shù) -->
渲染結(jié)果:
<button type="button" class="a-button">這里寫的東西會覆蓋子組件里的 slot 標(biāo)簽所在的位置</button>
可以應(yīng)用簡單的 slot 來達(dá)到為不同的按鈕填充文字的目的:
<a-button @click="handleClick">詳情</a-button>
<a-button @click="handleClick">搜索</a-button>
<!-- 渲染結(jié)果 -->
<button type="button" class="a-button">詳情</button>
<button type="button" class="a-button">搜索</button>
作用域插槽示例:
<!-- 子組件 -->
<div class="child">
<!-- slot 這個位置會在子組件被使用時被父組件傳來的 html 代碼覆蓋 -->
<!-- slot 上的 text/child 就相當(dāng)于傳給父組件的 props (假設(shè) name 為子組件的 data,name: someChild) -->
<slot text="hello from child" :child="name"></slot>
</div>
在父級中,具有特殊屬性 scope 的 <template> 元素,表示它是作用域插槽的模板。scope 的值對應(yīng)一個臨時變量名,此變量接收從子組件中傳遞的 prop 對象:
<!-- 父組件 -->
<div class="parent">
<child>
<template scope="props">
<span>hello from parent</span>
<span>{{ props.text }}</span>
<span>{{ props.child }}</span>
</template>
</child>
</div>
渲染結(jié)果:
<div class="parent">
<div class="child">
<span>hello from parent</span>
<span>hello from child</span>
<span>someChild</span>
</div>
</div>
03總結(jié):
應(yīng)用場景示例:elementui 的 button 組件中有簡單插槽的使用,table 組件則使用到了 作用域插槽。
<!-- button 組件 -->
<el-button>默認(rèn)按鈕</el-button>
<el-button type="primary">主要按鈕</el-button>
<el-button type="text">文字按鈕</el-button>
<!-- table 組件 -->
<el-table
:data="tableData">
<el-table-column
prop="zip"
label="郵編"
width="120">
</el-table-column>
<el-table-column
fixed="right"
label="操作"
width="100">
<template scope="scope">
<el-button
<!-- 可以通過 scope.$index 獲取到當(dāng)前行的索引 -->
@click.native.prevent="deleteRow(scope.$index)">
移除
</el-button>
</template>
</el-table-column>
</el-table>
04. router 使用小記
vue-router 的使用,簡單來說就是通過配置,實現(xiàn)在不同的 url 路徑下,頁面渲染不同的組件。具體查看文檔:vue-router 2。
使用示例
一級路由:
<!-- App.vue -->
<!-- 該組件為最高級父組件,使用 router-view 來根據(jù)路徑確定要顯示的子組件 -->
<template>
<div id="app">
<!-- router-view 位置用來顯示組件,如顯示下面的 index.vue -->
<router-view></router-view>
</div>
</template>
二級路由(路由可嵌套):
<!-- page/index.vue -->
<!-- 該組件包含一個頂部欄和側(cè)邊菜單欄,內(nèi)容區(qū)使用 router-view 來根據(jù) url 路徑顯示子組件 -->
<template>
<div class="index">
<!-- 頂部導(dǎo)航條 -->
<header class="main-header">
...
</header>
<!-- /頂部導(dǎo)航條 -->
<!-- 側(cè)邊導(dǎo)航欄 -->
<aside class="main-sidebar sidebar-gradient">
...
</aside>
<!-- /側(cè)邊導(dǎo)航欄 -->
<!-- 根據(jù)頁面一級菜單的點擊而進(jìn)行切換的內(nèi)容區(qū) -->
<transition name="fade">
<!-- router-view 位置用來顯示組件 -->
<router-view></router-view>
</transition>
<!-- /內(nèi)容區(qū) -->
</div>
</template>
router 配置:
// router/index.js
import Vue from 'vue';
import Router from 'vue-router';
// 引入組件
import index from 'page/index'; // 該組件包含一個頂部欄和側(cè)邊菜單欄,內(nèi)容區(qū)使用 router-view 來根據(jù) url 路徑顯示子組件
import notFoundComponent from 'page/404'; // 該組件為 404 頁面,當(dāng)你路由使用 history 模式時需要用到
import monitorIndex from 'page/monitor/index'; // 該組件為一個監(jiān)控頁面,用于顯示在 page/index.vue 頁面上的 router-view 處(即頁面的內(nèi)容區(qū)域)
Vue.use(Router);
// 定義 scrollBehavior 方法
const scrollBehavior = (to, from, savedPosition) => {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
export default new Router({
mode: 'history',
// mode 默認(rèn) hash 值,但是 hash (url中包含 # 符號)不太好看也不符合我們一般的網(wǎng)址瀏覽習(xí)慣
// 當(dāng)你使用 history 模式時,URL 就像正常的 URL,例如 http://yoursite.com/user/id,也好看!
linkActiveClass: 'active',
// 默認(rèn)值: 'router-link-active',就是當(dāng)前組件被激活,相應(yīng)路由會自動添加類 'router-link-active',這里是為了全局設(shè)置激活類名,如果不設(shè)置,直接用默認(rèn)的也是可以的
// 如:使用 router-link 組件來導(dǎo)航,通過傳入 `to` 屬性指定鏈接
// <router-link to="/foo">Go to Foo</router-link>
// <router-link> 默認(rèn)會被渲染成一個 `<a>` 標(biāo)簽,'/foo' 路由下的組件顯示時,該 a 標(biāo)簽上會自動添加類 'active'
scrollBehavior: scrollBehavior,
// 通過這個屬性(是個函數(shù)),可以讓應(yīng)用像瀏覽器的原生表現(xiàn)那樣,在按下 后退/前進(jìn) 按鈕時,簡單地讓頁面滾動到頂部或原來的位置,如果不設(shè)置,則組件切換時滾動條位置不變
routes: [
{
// 一級路由
path: '/',
component: index,
children: [
// 二級路由
// -----------默認(rèn)首頁-------------
// 當(dāng) / 匹配成功,monitorIndex 會被渲染在 index 的 <router-view> 中
{ path: '', component: monitorIndex, alias: 'index.html' },
// 這里的 alias 'index.html' 為當(dāng)前頁面的別名
// http://localhost:8080/index.html 等同于 http://localhost:8080/
// -----------監(jiān)控中心-------------
{
// 當(dāng) /monitor 匹配成功,
// monitorIndex 會被渲染在 index 的 <router-view> 中
path: 'monitor',
name: '監(jiān)控中心',
component: monitorIndex
}
]
},
// 同一個路徑可以匹配多個路由,此時,匹配的優(yōu)先級就按照路由的定義順序:誰先定義的,誰的優(yōu)先級就最高
// 因此下面的路由配置為備用,如果某個路徑未被配置顯示相應(yīng)的組件,則顯示 404 頁面
{ path: '*', component: notFoundComponent }
]
});
引入 router 配置:
// main.js
import Vue from 'vue';
// 引入 element ui 組件
import { Dropdown, DropdownMenu ...} from 'element-ui';
// 引入 App.vue
import App from './App';
// 引入 router 配置
import router from './router'; // 默認(rèn)會找到 router 文件夾下的 index.js 文件
// 引入項目圖標(biāo)的 sprite css,可以簡單的通過這種方式引入 css 文件
import './assets/css/sprite.css'
// 使用 element ui 組件
Vue.use(Dropdown)
Vue.use(DropdownMenu)
...
new Vue({
el: '#app',
router, // 使用 router 配置
template: '<App/>',
components: { App },
});
04總結(jié):
關(guān)于 vue-router 的使用,看文檔一般都能解決你的疑問,vue-router 2。
其他參考文章:Vue.js系列之vue-router(中)(4)
PS:使用 history 模式的話,還需要 后臺配置 支持。因為我們的應(yīng)用是個單頁客戶端應(yīng)用,如果后臺沒有正確的配置,當(dāng)用戶在瀏覽器直接訪問http://oursite.com/user/id就會返回 404(因為的確找不到該頁面),這就不好看了。并且在后臺配置后,還需要前端來提供 404 頁面,我上面的示例代碼中有提到,可供參考。
05. 測試接口
使用 Vue 開發(fā)單頁應(yīng)用時,前后端分離開發(fā),進(jìn)度不一。因此前端有時候就需要自己模擬接口的 json 文件,然后直接使用異步請求方法(如 ajax) 去獲取數(shù)據(jù),渲染頁面,測試代碼等。
Step 1:
在 項目根目錄/static/api/ 目錄下新建一個 test.json 文件,寫入模擬的接口數(shù)據(jù):
{
"status": true,
"data": {
...
}
}
Step 2:
在 .vue 組件文件里任意需要請求數(shù)據(jù)的方法里(如 created 鉤子,或者某個 methods 方法里)編寫相關(guān)代碼:
let vm = this;
// ajax 請求數(shù)據(jù)
$.ajax({
type: 'GET',
url: 'static/api/test.json',
data: '',
beforeSend: function() {
// 顯示 Loading
vm.loading = true;
},
complete: function() {
// 隱藏 Loading
vm.loading = false;
},
success: function(data) {
// 處理返回數(shù)據(jù)
...
},
error: function() {
// 數(shù)據(jù)請求失敗,給用戶適當(dāng)?shù)姆答佇畔? ...
},
dataType: 'json'
});
05總結(jié):
在后端尚未提供接口時,我都是用這個方法來測試前端獲取數(shù)據(jù)和處理數(shù)據(jù)的代碼是否正確。
3. 零碎問題
01. prop 傳值小技巧
我們可以為組件的 props 指定驗證規(guī)格。如果傳入的數(shù)據(jù)不符合規(guī)格,Vue 會發(fā)出警告。當(dāng)組件給其他人使用時,這很有用。
示例如下:
props: {
// 基礎(chǔ)類型檢測 (`null` 意思是任何類型都可以)
propA: Number,
// 多種類型
propB: [String, Number],
// 必傳且是字符串
propC: {
type: String,
required: true
},
// 數(shù)字,有默認(rèn)值
propD: {
type: Number,
default: 100
},
// 數(shù)組/對象的默認(rèn)值應(yīng)當(dāng)由一個工廠函數(shù)返回
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 自定義驗證函數(shù)
propF: {
validator: function (value) {
return value > 10
}
}
}
愚蠢的我每次想要傳 Number 或者 Boolean 類型的值到子組件時,都在父組件里定義好值,然后再綁定到子組件上:
// 這樣會報錯,因為 show type 為 Boolean,rows type 為 Number
// 默認(rèn)情況下直接傳值,子組件接收到的都是 String 類型
// template
<child show="true" rows="6"></child>
// 于是我這樣做:
// template
<child :show="show" :rows="rows"></child>
// script
show: true,
rows: 6
// 實際上可以直接這樣做:
// template
<child :show="true" :rows="6"></child>
// 官網(wǎng)如是說:如果想傳遞一個實際的 number,需要使用 v-bind ,從而讓它的值被當(dāng)作 JavaScript 表達(dá)式計算。
小技巧:當(dāng)某個 prop 類型為 Boolean 時,可以直接把該 prop 的名稱寫在組件上,默認(rèn)會傳 true,不寫的話默認(rèn)為 false。比如
<child show :rows="6"></child>這么寫,子組件內(nèi)部就能收到 show 為 true。
02. autoprefixer
有些人會問如何在項目里使用
autoprefixer插件,事實上使用vue-cli的webpack模板生成的項目里已經(jīng)帶有autoprefixer的使用了,如下圖:

03. build 時不生成 .map 文件
對項目進(jìn)行
npm run build操作后,發(fā)現(xiàn)生成的文件超大(比想象中的大),尤其是那些.map文件,不過,我們可以通過配置選擇不生成該類文件。
// 項目根目錄/config/index.js
var path = require('path')
module.exports = {
build: {
...
productionSourceMap: false, // 將該值設(shè)為 false,就不會生成 .map 文件了
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
...
},
dev: {
...
}
}
4. 總結(jié)
一句話,有空多看文檔,可以避免很多實踐中的問題。