包學會之淺入淺出Vue.js:升學篇(轉)
包學會之淺入淺出Vue.js:升學篇
蔡述雄 發表於 蔡述雄的專欄 24.6K在這篇文章中:
蔡述雄,現騰訊使用者體驗設計部QQ空間高階UI工程師。智圖圖片優化系統首席工程師,曾參與《眾妙之門》書籍的翻譯工作。目前專注前端圖片優化與新技術的探研。
上一篇《包學會之淺入淺出Vue.js:開學篇》中,我們初步瞭解單頁面元件這個概念,現在通過一個專案,來進一步解析元件的應用吧,Go~
需求背景
元件庫是做UI和前端日常需求中經常用到的,把一個按鈕,導航,列表之類的元素封裝起來,方便日常使用,呼叫方法只需直接寫上<qui-button></qui-button>
或者<qui-nav></qui-nav>
這樣的程式碼就可以,是不是很方便呢,接下來我們將要完成以下頁面:
這是我們元件庫的首頁,包含三個子頁面,按鈕頁面、列表頁面、導航頁面;點選進去子頁面會由路由來配置。先看我們的目錄結構:
pages目錄存放我們的頁面,包括首頁和三個子頁面;components目錄存放我們的具體元件,包括按鈕元件,箭頭元件,列表元件和導航元件(元件和頁面其實是一樣的檔案型別,只是由於功能不一樣,我們就叫不同的稱呼)
先看路由配置的程式碼吧!
路由配置
import Vue from 'vue'
import Router from 'vue-router' // 引用頁面模板->供路由使用 import index from '../pages/index.vue' import pageQuiButton from '../pages/pageQuiButton.vue' import pageQuiList from '../pages/pageQuiList.vue' import pageQuiNav from '../pages/pageQuiNav.vue' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'index', component: index }, { path: '/btn', name: 'btn', component: pageQuiButton }, { path: '/list', name: 'list', component: pageQuiList }, { path: '/nav', name: 'nav', component: pageQuiNav } ] })
有了上一篇的分析之後,這裡應該很容易看出來幾個路由地址
首頁:http://localhost:8080/#/
按鈕子頁:http://localhost:8080/#/btn
列表子頁:http://localhost:8080/#/list
導航子頁:http://localhost:8080/#/nav
具體每一頁的內容分別對應每一頁的.vue檔案,不知大家是否還記得入口頁App.vue
,這個檔案承載著一些公用的元素,還有就是一個路由容器,我們的首頁index.vue
到時候也是掛載在路由容器中的,看看App.vue
的程式碼
入口頁App.vue
<template>
<div id="app"> <h1 class="page-title"><a href="#/">開發元件庫</a></h1> <router-view></router-view> </div> </template> <script> export default { name: 'app' } </script> <style scoped> @import './assets/css/App.css'; </style>
簡單分析一下入口頁的程式碼,h1標籤是一個公用元素,也就是說到時候每個子頁面都會帶著這個h1,他的作用就是方便我們快速回到首頁,子頁面的內容會注入到router-view
中。這裡值得關注的地方是style標籤,我們可以在style標籤裡面直接寫樣式,也可以直接引入一個樣式檔案,scoped關鍵字表示這個樣式是私有的,也就是說,即使兩個元件寫著一樣的#app{}
樣式也不會衝突,程式會加上名稱空間,這也就是為什麼在script標籤中有個name引數。
首頁index.vue
<template>
<div class="mod-module mod-parallel"> <div class="img-list type-full"> <div class="img-box"> <p class="img-item"> <a class="page-link" href="#/btn">按鈕</a> </p> </div> <div class="img-box"> <p class="img-item"> <a class="page-link" href="#/list">列表</a> </p> </div> <div class="img-box"> <p class="img-item"> <a class="page-link" href="#/nav">導航</a> </p> </div> </div> </div> </template> <style scoped> @import './css/index.css'; </style>
首頁的程式碼也是非常簡單,和我們平時寫html差不多,就是幾個跳轉連結跳到對應的子頁面,程式執行的時候,會將<template>
標籤裡面的內容都注入到App.vue頁面中的router-view
標籤中,從而實現無重新整理的路由跳轉。
從下面的內容開始,我們的知識將會深入一些。我們先不急著看其他幾個子頁面,因為子頁面裡面只是引用對應的元件,所以我們先從元件開始入手。
按鈕元件quiButton.vue
<template>
<button class="qui-btn"> <span>{{msg}}</span> </button> </template> <script> export default { data:function(){ return { msg:'下載' } } } </script> <style scoped> @import './css/reset.import.css'; @import './css/qui-btn.import.css'; </style>
按鈕元件很簡單,就是一個正常的button標籤,script標籤中暴露這個元件的data屬性(data是Vue的屬性值,不是亂寫的~~)。當按鈕元件被初始化的時候,msg自定義屬性會被繫結到<span>
標籤中的{{msg}}
中,兩個花括號用來繫結屬性,這種寫法學過模版化前端程式碼的人應該都比較熟悉。這裡需要注意一個地方,如果不是元件的話,正常data的寫法可以直接寫一個物件,比如data:{ msg : ' 下載 ' }
,但由於元件是會在多個地方引用的,JS中直接共享物件會造成引用傳遞,也就是說修改了msg後所有按鈕的msg都會跟著修改,所以這裡用function來每次返回一個物件例項。
這就是一個非常簡單的按鈕元件,結構、樣式+文案。
這時候問題來了,按鈕中的文案我希望可以異化,不能每次都初始化一個叫做“下載”文案的按鈕吧,希望可以以屬性的方式來使用,比如這樣子寫就可以改變我們的按鈕文案:
<qui-btn msg="確定" class="small"></qui-btn>
沒問題,屬性的介面暴露只需要寫在prosp裡面就可以了,如下所示修改下script標籤的內容:
<script>
export default {
props: { msg: { default: '下載' } } } </script>
把屬性寫在props裡面,就可以暴露給其他頁面呼叫了,在元件中,props是專門用來暴露元件的屬性介面的,這裡給了一個預設值‘下載’
,後面我們要使用的話,只需要<btn msg="確認"></btn>
就可以修改按鈕的預設文案了。
我們在上一篇文章的開頭就講了Vue是資料驅動模式的,當我在btn結構寫上msg="確認"
的時候,對應script裡面的msg屬性就會自動修改了。
按鈕事件
按鈕總少不了點選事件吧,那在Vue中怎麼繫結事件呢,用methods屬性,看下程式碼:
<template>
<button class="qui-btn" v-on:click="btnClickEvent"> <span>{{msg}}</span> </button> </template> <script> export default { props: { msg: { default: '下載' } }, methods: { //繫結事件的關鍵程式碼 btnClickEvent: function(){ alert(this.msg); } } } </script>
methods屬性中可以寫任何的自定義函式,寫完之後繫結的方式也很簡單,在button上寫關鍵字v-on:click
,把對應的事件寫上就可以了,以上程式碼實現的就是點選按鈕彈出按鈕中的文案,v-XXX
是Vue裡的一些關鍵字,叫做指令,我們後面會慢慢學到更多的指令;v-on:click
可以縮寫為@click
,當然還有其他的事件比如v-on:tab
等等;
使用按鈕元件pageQuiButton.vue
現在我們大致做了一個按鈕元件了,那麼怎麼呼叫它呢,去到我們的pageQuiButton子頁面。
//pageQuiButton.vue
<template>
<div id="pageQuiButton"> <!--使用--> <qui-btn msg="確定" class="small"></qui-btn> </div> </template> <script> import quiBtn from '../components/quiButton.vue' /*引用*/ export default { name: 'pageQuiButton', components: { 'qui-btn': quiBtn /*註冊自定義標籤*/ } } </script>
從script開始解析,首先引入我們的元件賦值給變數quiBtn,使用時候直接將quiBtn作為物件的一部分寫進components屬性,這是Vue用來儲存引用元件的關鍵字,同時對應我們自定義的標籤 "qui-btn"
,完成這些操作之後,我們就可以在template中使用自定義的按鈕元件<qui-btn>
上面也說了用msg屬性來自定義按鈕的文案。完成之後,我們就可以在頁面中看到具體效果,點選按鈕彈出對應的文案。
上述我們將按鈕事件寫成預設的alert(this.msg)
,如果有些按鈕想要異化怎麼辦。之前說了msg屬性可以支援自定義,那麼按鈕的點選事件如何支援自定義呢?
//pageQuiButton.vue
//監聽子元件的事件
<qui-btn v-on:btnClickEvent="doSth" msg="我可以點選" ></qui-btn>
上面的程式碼在引用元件的時候,註冊了一個事件,這個btnClickEvent事件是之前我們在按鈕元件中繫結到按鈕的click事件中的,然後我們給這個事件一個自定義的方法doSth,同時,在script中宣告這個自定義的方法如下:
//pageQuiButton.vue
//頁面中引用子元件並監聽子元件的事件
<script>
import quiBtn from '../components/quiButton.vue' export default { name: 'pageQuiButton', components: { 'qui-btn': quiBtn }, methods: { doSth: function(){ alert('你點選了元件的click:btnClickEvent'); } } } </script>
專業一點的說,這種做法叫做監聽,由引用方(暫且叫做父元件)監聽子元件的內建方法;同時在子元件中,需要觸發這個事件,以下是在子元件中的關鍵程式碼:
//quiButton.vue
//子元件中的程式碼
<script>
export default { props: { msg: { default: '下載' } }, methods: { btnClickEvent: function(){ alert("先彈出預設的文案"); this.$emit('btnClickEvent');//關鍵程式碼父元件觸發自定義事件 } } } </script>
這裡的關鍵程式碼就是$emit
,也叫觸發機制,父元件監聽,子元件觸發。如果覺得繞,以下描述可能會比較好理解:小B(子元件)有一個電話號碼(子元件註冊的事件),有一天小B把電話號碼告訴了小A(父元件),讓小A打電話給他,於是小A就撥打了小B的電話號碼(監聽),這時候整個溝通流程沒有結束,必須要小B接聽了電話(觸發),兩人之間才算完成了打電話這件事情。
完成這步之後,引用方(父元件)就可以給不同子元件呼叫不同的事件處理了:
<qui-btn v-on:btnClickEvent="doSth1" msg="確定" ></qui-btn> <qui-btn v-on:btnClickEvent="doSth2" msg="取消" ></qui-btn> <script> /*這裡只是簡單展示*/ methods: { doSth1: function(){ alert('111'); }, doSth2: function(){ alert('222'); } } </script>
給按鈕加圖示
有時候單純的文案異化還不夠,比如一些按鈕是圖示+文字型別的,而且圖示還可能不一樣,那應該怎麼辦呢?
如果按鈕元件的結構除了開發時候預設的那些dom結構之外,允許我們在呼叫的時候新增一些自己想要的結構,那是不是解決了呢,是的,Vue早就為我們考慮了這一點,他就是slot標籤。
下面給我們的按鈕元件加上一段結構
//quiButton.vue
<template>
<button class="qui-btn" v-on:click="btnClickEvent"> <slot name="icon"></slot><!--重點在這裡--> <span>{{msg}}</span> </button> </template>
加入了關鍵字slot並賦予一個name值之後,我們再看看引用如何使用
//pageQuiButton.vue
<qui-btn msg="下載" class="with-icon"> <img slot="icon" class="ico" src="xxx.png" /> </qui-btn>
img上有個關鍵字slot="icon"
對應元件中的name="icon"
,渲染的時候,會將img整個替換掉元件中的對應name的<slot>
標籤,其實很好理解,slot的翻譯是插槽的意思,相當於把img這塊內容插到一個名叫icon的插槽裡面去。
中場休息一下
學到這裡,我們已經學會了用props給按鈕自定義文案,用on和emit給按鈕自定義點選觸發,用slot給按鈕新增一些自定義結構。當你回頭去翻文件的時候,你會發現props,事件,slot這三樣剛好就是學習元件通訊中最最最關鍵的三個環節。將這三個環節以實際案例解析出來後,好像也沒有那麼難了吧~!
上述我們已經討論瞭如何製作一個按鈕元件,以及如何使用我們的按鈕元件。
接下來我們通過製作一個導航元件,來了解Vue中對於for迴圈的巧妙使用。
導航元件quiNav.vue
我們將完成這樣一個導航元件,點選導航中的tab,可以給當前tab加上一個active類,同時切換底部的黃色滑條,並且輸出當前tab的文案,同時支援自定義事件。
由於在現實專案中,我們導航的tab個數是不定的,所以製作元件的時候,我們希望可以暴露一個屬性來支援導航的tab個數,而tab的長相和應用其實是一樣的,那麼這時候我們可以用一個for迴圈來輸出每一個tab。先看看關鍵程式碼:
//quiNav.vue
<template>
<div class="qui-nav nav-type-1"> <a v-for="(item, index) in items" ><!--關鍵程式碼v-for--> <span class="nav-txt">{{item.text}}</span> </a> </div> </template> <script> export default { data:function(){ return { items:[ { text: '首頁', active : true }, { text: '列表', active : false }, { text: '關於', active : false }, { text: '招聘', active : false } ] } } } } </script>
該段程式碼的關鍵地方在於a標籤上v-for
關鍵字(還記得我們在前面說過的v-on繫結事件嗎,v-XXX關鍵字是Vue預留的)可以把它理解為js中的for in 迴圈,items是我們在data裡面定義的物件(還記得為什麼data要寫在function中返回嗎?)。v-for="(item,index) in items"
暴露了item和index兩個介面,這是Vue提供的,代表items中的每一項以及該項對應的下標,接著我們就可以在標籤中使用繫結{{item.text}}
了。
這段程式碼理解了之後,我們再延伸一個動態新增class的概念。我們希望每個tab都有預設的class類名(比如nav-item
類),在點選每個tab的時候,當前tab新增active類,其他的tab刪除這個active類。在Vue怎麼實現呢?
動態類名
//quiNav.vue
<template>
<div class="qui-nav nav-type-1"> <a v-for="(item, index) in items" :class="[commonClass,item.active ? activeClass : '']" > <span class="nav-txt">{{item.text}}</span> </a> </div> </template> <script> export default { data:function(){ return { commonClass:'nav-item', activeClass:'active', items:[ ...//資料 ] } } } </script>
在template中添加了一句關鍵程式碼
:class="[commonClass,item.active ? activeClass : '']"
:class
給元件繫結一個class屬性(類似jQuery中的attr方法),這裡的寫法是縮寫,他的全拼應該是v-bind:(又一個v-XXX寫法)。注意到最前面有個冒號,:class=XXX
和class=XXX
的區別在於不帶冒號的是靜態的字串繫結,帶冒號的是動態的變數繫結。我們給class綁定了一個數組,這個陣列帶有變數,先看commonClass,這個變數在data中定義了,然後陣列的第二個元素是一個JS的三元運算子:item.active?activeClass:''
,當每個item中的active值為true時,繫結activeClass變數對應的類,如果為false,則為空。最後的結果是當item.active為true時候,tab的class值為'nav-item active'
,當為false,就只有'nav-item'
。
上面的程式碼可以理解的話,那麼我們切換tab的active類,就轉換為修改每個item裡面的active的值(再次體現資料驅動)。
那麼問題來了,怎麼去修改每個item裡面的active值呢?沒錯,給每個tab繫結一個點選事件,當點選事件觸發的時候,修改當前tab對應item的active值。於是程式碼變成了如下:
<template>
<div class="qui-nav nav-type-1"> <a v-for="(item, index) in items" :class="[commonClass,item.active ? activeClass : '']" v-on:click="navClickEvent(items,index)" > <span class="nav-txt">{{item.text}}</span> </a> </div> </template> <script> export default { data:function(){ return { commonClass:'nav-item', activeClass:'active', items:[ { text: '首頁', active : true }, ...... ] } }, methods:{ navClickEvent:function(items,index){ /*預設切換類的動作*/ items.forEach(function(el){ el.active = false; }); items[index].active = true; /*開放使用者自定義的介面*/ this.$emit('navClickEvent',items,index); } } } </script>
我們利用for迴圈給每個a標籤綁定了一個click事件,對應methods中定義的navClickEvent,接收兩個引數items和index(你也可以傳人item和index,看個人程式碼喜好),然後當點選的時候,把items中的每個item.active置為false,把當前的tab的active值置為true,這樣就可以動態切換active類了。最後再觸發一次自定義事件(參考按鈕製作自定義事件)。
以上就是我們導航元件的內容了,回想下我們做了啥?for迴圈輸出每個tab,為每個tab繫結動態的class類名,同時在點選事件中動態切換類(底部的小黃條其實是利用active類做的CSS)
小結
回顧下我們這一篇章都學了什麼內容。
- 頁面路由的配置
- 按鈕元件自定義屬性props
- 按鈕元件自定義事件 $on $emit
- 按鈕元件自定義子塊slot
- for迴圈實現導航元件
- 動態類名
上述內容已經基本上涵蓋了元件的重要知識點,主要是父元件(頁面)和子元件之間的呼叫和通訊(資料互動繫結),好好消耗一下我們會發現,其實Vue的總體邏輯思想和jQuery是一樣的,畢竟最後都回歸到javascript,只是由於程式碼設計角度的不同,我們可能看到和以前呼叫jQuery時候的寫法不一致,但其實都有對方的影子在裡面,相信理解了Vue的程式碼思想之後,以後我們學習React等其他類似的框架的時候,也會比較得心應手了。
下一篇文章《包學會之淺入淺出Vue.js:結業篇》中,我們將會學習如何用多個元件來組成一個大的元件,也就是真正意義上的父子元件之間的關係。再忍耐一下,就可以出山了,新領域的大門就在前面,讓我們大步往前跨吧。
文末附上所有相關程式碼和官方文件地址~~~