我記得當時我拿起CakePHP,我很喜歡,開始使用它是多麼容易。這些文件不僅結構合理,詳盡無遺,而且使用者友好。多年以後,這正是我在Vue.js中感受到的。然而,與Cake相比,Vue文件仍然缺少一件事:一個真實的專案教程。
無論框架的文件記錄如何,這對每個人來說都是不夠的。閱讀概念並不總是有助於瞭解大局或瞭解如何使用它們來實際製作某些東西。如果您像我一樣,通過製作一些可用的元件來更好地學習,並在編碼時參考文件,幫助記憶文件和熟練使用Vue。
在本教程中,我們將構建星級評分系統元件。我們將學習幾種Vue.js中的概念,我們會在專案中使用到他們,並將討論為什麼我們使用它們。
這篇文章深入介紹瞭如何以及為何。它旨在幫助您掌握Vue.js的一些核心概念,並教您如何為未來的專案做出設計決策。如果您想了解整個思考過程,請繼續閱讀。否則,您可以檢視CodeSandbox上的最終程式碼。
入門
Vue.js,當然你會以自己作為一個簡單的指令碼執行而覺得已經不錯了,但是當你想使用單檔案元件時情況有點不同。當然,你不一定必須構建以元件的這種方式。您可以完美地使用定義全域性元件Vue.component
。問題是,這需要權衡,例如必須使用字串模板,沒有範圍的CSS支援,也沒有構建步驟(因此,沒有前處理器)。然而,我們希望更深入地學習如何構建一個可以在實際專案中使用的實際元件。出於這些原因,我們將採用由Webpack提供支援的實際設定。
為了簡化操作並縮短配置時間,我們將使用vue-cli和webpack-simple Vue.js模板。
首先,您需要在全域性安裝vue-cli。啟動終端並鍵入以下內容:
npm install -g vue-cli
複製程式碼
您需要繼續輸入:
vue init webpack-simple path/to/my-project
複製程式碼
你會在這個過程中被問到幾個問題。選擇除“使用sass”以外的所有內容,都是為預設值。然後,vue-cli將初始化專案並建立package.json檔案。完成後,您可以導航到專案的目錄,安裝依賴項並執行專案:
cd path/to/my-project
npm install
npm run dev
複製程式碼
不出意外!Webpack將開始在埠上提供專案8080(如果可用,8080埠沒有被其他程式佔用)並在瀏覽器中觸發它。如果一切順利,你應該看到像這樣的歡迎頁面。
短暫的暫停 - 除錯工具
幾乎!要正確除錯Vue.js元件,您需要正確的工具。繼續安裝Vue.js devtools瀏覽器擴充套件程式(Firefox / Chrome / Safari)。
你的第一個元件
最好的功能之一是Vue.js單檔案元件(SFC)。它們允許您在一個檔案中定義元件的結構,樣式和行為,而沒有混合HTML,CSS和JavaScript的常見缺點。
SFC以.vue副檔名結尾,具有以下結構:
<template>
<!-- Your HTML goes here -->
</template>
<script>
/* Your JS goes here */
</script>
<style>
/* Your CSS goes here */
</style>
複製程式碼
讓我們來建立我們的第一個元件:建立一個Rating.vue
檔案/src/components
,然後複製/貼上上面的程式碼片段。然後,開啟/src/main.js
並調整現有程式碼:
import Vue from 'vue'
import Rating from './components/Rating'
new Vue({
el: '#app',
template: '<Rating/>',
components: { Rating }
})
複製程式碼
最後,為您的Rating.vue
新增一些HTML:
<template>
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
</template>
複製程式碼
現在檢視瀏覽器中的頁面,您應該看到列表!Vue.js將您的<Rating>
元件附加到#app
元素中index.html
。如果檢查HTML,則應該看不到該#app
元素的符號:Vue.js將其替換為Rating
元件。
旁註:您是否注意到您甚至不需要重新載入頁面?那是因為Webpack的vue-loader帶有熱過載功能。與實時重新載入或瀏覽器同步相反,熱重新載入不會在每次更改檔案時重新整理頁面。相反,它會監視元件更改並僅重新整理它們,保持狀態不變。
現在我們已經花了一些時間來設定,但現在是我們實際編寫有意義的程式碼的時候了。
模版template
我們將使用vue-awesome,一個用Font Awesome圖示構建的Vue.js的SVG圖示元件。這允許我們只載入我們需要的圖示。繼續使用npm(或Yarn)安裝它:
npm install vue-awesome
複製程式碼
然後編輯您的元件,如下所示:
<template>
<div>
<ul>
<li><icon name="star"/></li>
<li><icon name="star"/></li>
<li><icon name="star"/></li>
<li><icon name="star-o"/></li>
<li><icon name="star-o"/></li>
</ul>
<span>3 of 5</span>
</div>
</template>
<script>
import 'vue-awesome/icons/star'
import 'vue-awesome/icons/star-o'
import Icon from 'vue-awesome/components/Icon'
export default {
components: { Icon }
}
</script>
複製程式碼
好吧好吧,讓我們放慢腳步並解釋所有這些內容?
Vue.js使用原生ES6模組來處理依賴項和匯出元件。<script>
塊中的前兩行表示單獨匯入圖示,因此您最終不會在最終bundle(資源)中找到您不需要的圖示。第三個Icon元件是從vue-awesome
中匯出的,因此您可以在您的元件中使用它。
Icon也是一個Vue.js的單檔案元件(SFC),就像我們正在構建的元件那樣。如果你開啟檔案,你會發現它具有與我們完全相同的結構。
該export default
匯出物件文字作為元件的檢視模型。我們在當前的components
中註冊了Icon
元件,因此我們可以在我們的本地使用它。
最後,我們在<template>
使用HTML它並傳遞了一個name屬性來定義我們想要的圖示。元件可以通過將它們轉換為kebab-case(例如:MyComponent
成為`)來用作自定義HTML標記。我們不需要在元件內巢狀任何東西,因此我們使用了自閉合標記。
旁註:您是否注意到在HTML周圍新增了一個<div>
包裝?那是因為我們還在根級別新增了一個計數器<span>
,而Vue.js中的元件模板只接受一個根元素。如果你不遵循這個規則,你會收到編譯錯誤。
樣式CSS
如果你已經使用過CSS一段時間了,你知道其中一個主要問題是必須處理它的全域性性。巢狀一直被認為是解決這個問題的方法。現在我們知道它可以很快導致特殊性問題,使得樣式難以覆蓋,亦或無法重用,以及是擴充套件的噩夢。
像BEM這樣的方法被髮明來通過名稱空間類來規避這個問題並保持低特異性。在一段時間內,它是編寫乾淨且可擴充套件的CSS的理想方式。然後,像Vue.js或React這樣的框架和庫出現了,並帶來了範圍樣式。
React具有樣式化的元件,Vue.js具有元件範圍的CSS。它允許您編寫特定於元件的CSS,而無需提供什麼特定神奇的技巧。您使用“普通”類名編寫常規CSS,並且Vue.js通過將資料屬性分配給HTML元素並將其附加到已編譯樣式來確定處理範圍。
讓我們在元件上新增一些簡單的類:
<template>
<div class="rating">
<ul class="list">
<li class="star active"><icon name="star"/></li>
<li class="star active"><icon name="star"/></li>
<li class="star active"><icon name="star"/></li>
<li class="star"><icon name="star-o"/></li>
<li class="star"><icon name="star-o"/></li>
</ul>
<span>3 of 5</span>
</div>
</template>
複製程式碼
並新增上樣式:
<style scoped>
.rating {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
font-size: 14px;
color: #a7a8a8;
}
.list {
margin: 0 0 5px 0;
padding: 0;
list-style-type: none;
}
.list:hover .star {
color: #f3d23e;
}
.star {
display: inline-block;
cursor: pointer;
margin: 0 1px;
}
.star:hover ~ .star:not(.active) {
color: inherit;
}
.active {
color: #f3d23e;
}
</style>
複製程式碼
你看到了scoped屬性了嗎?這就是告訴Vue.js,如何確定樣式範圍的原因,因此它們不會洩漏到任何其他地方。如果您正確地在index.html
複製/貼上HTML程式碼,您會注意到您的樣式不生效:那是因為它們的作用域是元件!?
那麼怎麼樣預處理呢?
Vue.js可以輕鬆地從普通的CSS切換到您最喜歡的前處理器模式。您只需要正確的使用Webpack loader
,並在<style>
塊上設定簡單的屬性。還記得我們在生成專案的時候,問我們是否使用sass
嗎,如果選擇"是"使用sass。同時vue-cli已經為我們安裝並配置了sass-loader。現在,我們需要做的就是在<style>
新增lang="scss"
標記。
我們現在可以使用Sass進行編寫元件級別風格的,通過import
引入Sass,就像使用import
引入變數,顏色定義等。如果你喜歡的縮排語法(或“sass”符號),只需lang
屬性切換scss
為sass
。
behavior - 行為
現在我們的元件看起來很好,是時候讓它工作了。目前,我們有一個硬編碼模板。讓我們設定一些初始模擬狀態並調整模板以便它反映出來:
<script>
...
export default {
components: { Icon },
data() {
return {
stars: 3,
maxStars: 5
}
}
}
</script>
複製程式碼
模版修改:
<template>
<div class="rating">
<ul class="list">
<li v-for="star in maxStars" :class="{ 'active': star <= stars }" class="star">
<icon :name="star <= stars ? 'star' : 'star-o'"/>
</li>
</ul>
<span>3 of 5</span>
</div>
</template>
複製程式碼
我們在這裡做的是使用Vue data
來設定元件狀態。您定義的每個屬性都會跟著data中的屬性進行繫結:如果它發生變化,它將反映在檢視中。
我們正在製作一個可重用的元件,因此data需要是一個工廠函式而不是一個物件字面量。通過這種方式,我們獲得了一個新物件,而不是對將在多個元件之間共享的現有物件的引用。
我們的data工廠返回兩個屬性:stars表示當前“啟用”星數量,maxStars表示總的星星數量。從這些,我們調整了我們的模板,以便它反映實際元件的狀態。Vue.js附帶了一系列指令,可以讓您在模板中新增表示邏輯,而無需將其與純JavaScript混合使用。該v-for指令遍歷任何可迭代物件(陣列,物件文字,對映等)。它也可以將數字作為重複x次的範圍。這就是我們所做的v-for="star in maxStars",所以我們<li>
對元件中的每個星都有一個。
您可能已經注意到某些屬性以冒號為字首:這是該v-bind指令的簡寫,它將屬性動態繫結到表示式。我們本來可以用它的不省略形式寫出來:v-bind:class
。
當星號處於啟用狀態時,我們需要
active
類。在我們的例子中,這意味著每個<li>
索引都應該小於star
才能附加active
類。我們在:class
指令中使用了一個表示式,只在當前star
小於stars
的時候追加active
。我們使用相同的條件,這次使用三元運算子來定義與Icon元件一起使用的圖示:star
或star-o
。
計數器應該如何
既然我們的star列表與實際資料繫結,那麼我們就應該為計數器做同樣的事了。最簡單的方法是使用“鬍子語法”進行文字插值:
<span>{{ stars }} of {{ maxStars }}</span>
複製程式碼
很直白,不是嗎?現在在我們的例子中,這可以解決問題,但如果我們需要更復雜的JavaScript表示式,最好在計算屬性中對其進行抽象。
export default {
...
computed: {
counter() {
return `${this.stars} of ${this.maxStars}`
}
}
}
複製程式碼
這裡有點矯枉過正。我們可以使用模板內表示式可以保持可讀性。然而,當您必須處理更復雜的邏輯時,請記住計算屬性。
我們需要做的另一件事是提供一種方法來隱藏計數器,如果我們不想要它。最簡單的方法是使用v-if
帶有布林值的指令。
<span v-if="hasCounter">{{ stars }} of {{ maxStars }}</span>
複製程式碼
data中新增hasCounter屬性
export default {
...
data() {
return {
stars: 3,
maxStars: 5,
hasCounter: true
}
}
}
複製程式碼
互動
我們差不多完成了,但我們仍然需要實現元件中最有趣的部分:反應性。我們將使用v-on
處理事件的Vue.js指令,以及methods
一個可以附加所有方法的Vue.js屬性。
<template>
...
<li @click="rate(star)" ...>
...
</template>
複製程式碼
methods中
export default {
...
methods: {
rate(star) {
// do stuff
}
}
}
複製程式碼
我們在上面新增了一個@click
屬性<li>
,這是一個簡寫v-on:click
。該指令包含我們在methods元件屬性中定義的rate方法的呼叫。
“等一下......這看起來非常熟悉HTML的onclick屬性。在HTML中使用內聯JavaScript不是一種過時的錯誤做法嗎?“
確實如此,但即使語法看起來很像onclick
,比較兩者也是一個錯誤。當您構建Vue.js元件時,您不應將其視為分離的HTML / CSS / JS,而應將其視為使用多種語言的一個元件。當專案在瀏覽器中編譯後,所有HTML和指令都編譯為純JavaScript。如果在瀏覽器中檢查呈現的HTML,則不會看到任何指令的跡象,也不會看到任何onclick屬性。Vue.js編譯了您的元件並建立了正確的繫結。這也是您可以直接從模板訪問元件上下文的原因:因為指令繫結到檢視模型。與具有單獨HTML的傳統專案相反,模板是元件的組成部分。
回到我們的rate
方法。我們需要在click時候修改stars為當前li
的索引,所以我們從@click
指令傳遞索引,我們可以執行以下操作:
export default {
...
methods: {
rate(star) {
this.stars = star
}
}
}
複製程式碼
在瀏覽器中檢視頁面並嘗試點選星標:它有效!
如果您在瀏覽器devtools中開啟Vue皮膚並選擇該元件,您將在單擊星標時看到資料發生變化。這表明您的stars屬性是被繫結的:當您對其進行修改時,它會將其更改分派給檢視。這個概念稱為資料繫結,如果你曾經使用過像Backbone.js或Knockout這樣的框架,你應該熟悉它。不同之處在於Vue.js與React一樣,僅在一個方向上執行:這稱為單向資料繫結。但這個話題又可以用另一篇文章來討論了?
到這個點上,我們可以稱之為完成,但我們可以做更多的工作來改善使用者體驗。
現在,我們實際上不能給出零等級,因為點選一個星標將stars
設定為其索引,而li
的索引永遠大於0。更好的做法是重新點選同一顆星並讓它切換當前狀態而不是保持活躍狀態。
export default {
...
methods: {
rate(star) {
this.stars = this.stars === star ? star - 1 : star
}
}
}
複製程式碼
現在,如果點選的星的索引等於當前值stars,我們減少它的值。否則,我們為其賦值star。
如果我們想徹底,我們還應該新增一層保護,以確保stars永遠不會分配一個沒有意義的值。我們需要確保stars永遠不會少於0,永遠不會超過maxStars,並且這是一個合適的數字。
export default {
...
methods: {
rate(star) {
if (typeof star === 'number' && star <= this.maxStars && star >= 0) {
this.stars = this.stars === star ? star - 1 : star
}
}
}
}
複製程式碼
passing props 屬性傳遞
現在,元件的資料在data屬性中是硬編碼的。如果我們希望我們的元件實際可用,我們需要能夠從其例項傳遞自定義資料。在Vue.js中,我們用props可以做到。
export default {
props: ['grade', 'maxStars', 'hasCounter'],
data() {
return {
stars: this.grade
}
},
...
}
複製程式碼
並在main.js
:
new Vue({
el: '#app',
template: '<Rating :grade="3" :maxStars="5" :hasCounter="true"/>',
components: { Rating }
})
複製程式碼
這裡有三件事需要注意:
首先,我們使用v-bind
簡寫來從元件例項傳遞props:這就是Vue.js所謂的動態語法。當您想要傳遞字串值時,您不需要它,使用文字語法(不帶v-bind的正常屬性)將起作用。但在我們的例子中,由於我們傳遞數字和布林值,所以我們這樣做很重要。
props
和data
特性是在編譯時合併了,所以props中的屬性會被檢視模型中的標籤繫結(如果定義了)。但出於同樣的原因,我們不能使用相同的名稱props和data屬性,否則data中的屬性會被props中的屬性覆蓋。
最後,我們定義了一個grade props,並通過它作為一個data的stars屬性。之所以我們這樣做,而不是直接使用grade props,是因為當我們改變等級時,star會發生修改。在Vue.js中,props是從父母元件傳遞給孩子元件,而不是相反,所以你不會意外地改變父母的狀態。這將違反單向資料流原則並使事情更難除錯。這就是為什麼你應該不嘗試修改子元件內的屬性。相反,定義一個data使用prop的初始值作為自己的本地屬性,也是一個很好的選擇。
最後的修改
我們將使用Vue.js中的一個很不錯的功能: prop validation
Vue.js允許您在將資料傳遞給元件之前對其進行控制。您可以執行以下四項主要操作:檢查型別,要求定義prop,設定預設值以及執行自定義驗證。
export default {
props: {
grade: {
type: Number,
required: true
},
maxStars: {
type: Number,
default: 5
},
hasCounter: {
type: Boolean,
default: true
}
},
...
}
複製程式碼
我們使用型別檢查來確保將正確型別的資料傳遞給元件。如果我們忘記使用動態語法傳遞非字串值,這將特別有用。我們還確保grade
是符合元件的給定要求進行傳遞的。對於其他道具,我們定義了預設值,因此即使沒有傳遞自定義資料,元件也能正常工作。
現在我們可以通過執行以下操作來例項化元件:
<Rating :grade="3"/>
複製程式碼
就是這樣!您建立了第一個Vue.js元件,並探討了很多概念,包括生成與樣板謨VUE-CLI,單檔案元件,在元件資料傳遞,scope style,指令,事件處理,計算屬性,Vue methods,單方式資料流,props和prop validation。而這僅僅是Vue.js所提供的淺顯的功能!
這是一個非常密集的教程,所以如果你不理解一切,不要擔心。再次閱讀,在各部分之間暫停,探索並擺弄CodeSandbox上的程式碼。如果您對本教程有任何疑問或評論,請不要猶豫,在微博上@我!
結尾
如果您有幸已經讀到這裡,相信一定對您有所幫助。
本文為翻譯文,並新增一些自己的討論,很多地方不流暢,請見諒。
本文的原始碼rating-vue-component,如果對您有用,希望您給個star