從零到部署:用Vue和Express實現迷你全棧電商應用(三)

NO IMAGE

這篇文章中,我們將講解 Vue 實例的 Props 和 Methods,接著我們又講解了最常見的 Vue 模板語法,並通過實例的方式將這些模板語法都實踐了一番,最後我們講解了 Vue 組件的組合,並完成了我們的發表商品頁面。

歡迎閱讀《從零到部署:用 Vue 和 Express 實現迷你全棧電商應用》系列:

用模板語法和雙向綁定實現數據的添加

當我們完成了商城應用的基本頁面框架之後,我們就可以開始考慮具體頁面的內容了。首先我們要考慮的就是數據的來源,即添加商品頁面。有了添加商品的入口,我們就可以展示商品列表,獲取商品詳情,甚至是修改商品信息。

不過在此之前,我們打算先複習一下 Vue 的一些重要知識點。如果你已經很熟悉了,可以直接跳到下面實現 ProductForm.vue 的代碼部分。

Vue 實例:Props 和 Methods

Props

props 是 Vue 進行組件之間傳參的形式。比如我們有如兩個組件 New.vueProductForm.vue,在 New.vue 組件中需要使用到 ProductForm.vue 組件。其中 New.vue 組件是用來創建商品的,它的代碼大致是這樣的:

import ProductForm from '@/components/ProductForm.vue';
<ProductForm :manufacturers="manufacturers" />

它需要給 ProductForm.vue 組件傳遞一個 manufacturers 屬性,以確保我們在創建商品時,可以選擇這個商品所屬的製造商,接著我們就可以在 ProductForm.vue 中的 props 中取到這個 manufacturers 屬性。ProductForm.vue 的代碼大致是這樣的:

<template>
<!-- 模板部分 -->
</template>
<script>
export default {
props: ['manufacturers'],
}
</script>

可以看到,我們在 ProductForm.vuescript 部分導出的對象裡面找到 props 屬性,然後取到 manufacturers 屬性。

Methods

然後是 methodsmethods 是用來定義在組件中會用到的一些方法,如果說我們前面提到的 data ,是從數據從邏輯層(JS)向視圖層(Html)流動的話,那麼這裡的 methods 就是視圖層觸發事件,如 click、submit等,反過來修改邏輯層的數據的方法,methods 使得數據可以雙向流動。

讓我們在完善一下我們的 ProductForm.vue ,看一下 Methods 在 Vue 中是如何運作的:

<template>
<form @submit.prevent="saveProduct">
<!-- 其他表單,如 input 等 -->
<div class="form-group new-button">
<button class="button">Add Product</button>
</div>
</form>
</template>
<script>
export default {
data: { isSaved: false },
props: ['manufacturers'],
methods: {
saveProduct() {
this.isSaved = true;
// 完成一些保存創建商品的邏輯 ...
}
}
}
</script>

可以看到,我們可以通過在 template (視圖層)通過點擊提交按鈕,發起表單提交事件,進而調用在 script 中定義在 methods 屬性中的 saveProduct 方法,這個方法可以進一步修改定義在定義在 data 屬性中的數據;甚至如果父組件 New.vue 傳遞了方法(以 props 的形式)給 ProductForm.vue 組件,我們可在 saveProduct 調用這個傳遞下來的方法,進而可以影響到父組件 New.vue 中的數據。我們將在後面的正式實現 ProductForm 組件時講解到它。

模板語法:v-on

接下來我們再來談一談 v-bind 和 v-on 。

在 Vue 中,我們通過 v-on 的方式接管了之前在 HTML 中 onEvent

比如之前我們在 HTML 中的寫法是這樣的:

<div onclick="alert('I love tuture')">
Hello Tuture
</div>

現在在 Vue 的模板語法中我們需要寫出這樣:

<div v-on:click="alert('I love tuture')">
Hello Tuture
</div>

類似的 onEvent 都要改成 v-on:Event。然後這樣寫顯得比較冗餘,所以 Vue 支持簡化寫法,用 @ 替換 v-on: 部分,我們就可以寫出這樣:

<div @click="alert('I love tuture')">
Hello Tuture
</div>

調用事件之後我們一般有一些這樣的操作,比如禁用瀏覽器默認行為,然後自己去處理事件,獲取後端數據,以前我們會這樣寫:

<div onclick="saveProduct()">
Hello Tuture
<script>
var saveProduct = function (e) {
e.preventDefault();
// do something you like
}
</script>

但是這樣寫又顯得特別繁瑣了,Vue 也覺得這樣可以簡化,於是我們直接將這些禁止默認行為的調用作為綁定事件的屬性來進行處理,於是乎在 Vue 中我們可以寫出這樣:

<template>
<div @click.prevent="saveProduct">
Hello Tuture
</div>
</template>
<script>
export default {
methods: {
saveProduct() {
// do something you like
}
}
}
</script>

不知道看了上面的長文,你有沒有一點暈,不管你暈不暈,我是得喝口水緩一下。 – v –

模板語法:v-bind

我們已經看到在 Vue 模板中我們可以使用如下的功能:

  • {{}} 插值語法將 data 渲染到 HTML 元素內容中
  • v-on 或者簡化寫法 @ ,等用來取代 HTML 的事件綁定

有了上面的功能,我們可以讓 HTML 動起來了,但是還缺點什麼,比如我們的 HTML 屬性,如 idclass 等,是不是也能動態的獲取變化值,你還別說,還真的可以,Vue 模板語法為我們提供了 v-bind 用於動態綁定屬性值,我們來看個例子:

<template>
<option v-bind:id="_id"  v-bind:value="value" />
</template>
<script>
export default {
data: { _id: '1', value: "Xiaomi" },
}
</script>

可以看到,我們在 script 中導出的對象屬性 data 中,定義了 _idvalue 值,然後我們通過在 <template> 模板中使用 v-bind 語法動態的給 option 標籤的 idvalue 屬性賦值,最後的結果看起來是這樣的:

<option id="1" value="Xiaomi" />

當然,當需要綁定的屬性多了,每次都寫 v-bind 顯得相當繁瑣,所以 Vue 為我們提供了 v-bind 的簡潔語法 :,即我們之前的綁定語法從 v-bind:id="_id" 變成了 :id="_id"

上面的代碼用簡潔語法改寫如下:

<template>
<option :id="_id"  :value="value" />
</template>
<script>
export default {
data: { _id: '1', value: "Xiaomi" },
}
</script>

模板語法:v-model 雙向綁定

前面我們提到通過 {{}} 插值語法渲染來自 data 的數據實現了邏輯層向視圖層的數據流動,通過 methods 在視圖層操作邏輯層的數據,實現了視圖層的數據向邏輯層的數據流動,從而達到了雙向綁定,當我們的應用越來越複雜,我們會發現這樣的數據雙向流動會越來越頻繁,而且粒度也會大小不一,有很多單純修改某個值的方法調用就會顯得特別繁雜,因此 Vue 通過提供 v-model 進行了視圖層和邏輯層的雙向綁定,讓我們來看個例子:

<template>
<!-- 其他代碼 ... -->
<input
type="text"
placeholder="Name"
v-model="name"
/>
<!-- 其他代碼 ... -->
</template>
<script>
export default {
data: { name: 'ProductForm' },
}
</script>

這裡我們通過申明 v-mode 將此 input 的值和我們在 Vue 實例中的 modelname 屬性進行了雙向綁定,即當 data 中的 name 發生變化,input 的值也會跟著變化,當 input 的值發生變化,我們 data 中的 name 的值也會被修改,這一切都是自動發生的,不需要我們額外的添加 methods 裡面的方法調用來手動修改。

模板語法:循環

好了,Vue 替我們接管了 HTML 元素屬性值、事件處理、元素內容,這些都還只屬於原來 HTML 的部分,它更強大的一點就是將 JS 的功能引入了模板語法中,使得我們可以實現類似循環,條件選擇操作等功能。

接下來我們先來看一下 Vue 為我們提供的 “循環” 模板語法, 它使得我們可以快速渲染大量具有相似結構的數據,比如渲染一個數組的數據,生成一個 HTML 元素列表,這在我們平時看到的新聞 App 裡面很常見,我們瀏覽新聞時,發現其實每條新聞的結構都很相似,並且有很多條新聞(可能多大幾百上千條),如果每一條我們都手動寫 HTML 代碼的話,無疑顯得相當繁瑣,並且數據一多,我們手動就顯得無能為力了,而 Vue 為我們提供的 “循環” 模板語法,使得我們可以通過非常簡單的寫法就可以渲染大量數據,我們來看個例子:

<!--
manufacturers = [
{ _id: 1, name: 'Apple' },
{ _id: 2, name: 'Xiaomi' }
]
model = { _id: 1, name: 'Apple' }
-->
<template v-for="manufacturer in manufacturers">
<option :value="manufacturer._id" :selected="manufacturer._id == model._id">{{manufacturer.name}}</option>
</template>

最後渲染的結果為:

<option value="1" selected="true">Apple</option>
<option value="2" selected="false">Xiaomi</option>

注意到,如果我們在寫 “循環” 語法時,使用了一個額外的標籤 template 來包裹我們需要渲染的 HTML 元素,這也是 Vue 推薦的寫法;我們在 template 標籤的屬性上添加 v-for 然後給它賦值 "manufacturer in manufacturers",通過這樣的形式進行列表數據的遍歷,每次從 manufacturers 中取一個元素,並賦值給 manufacturer ,然後我們就可以在 option 標籤中使用 manufacturer 和我們定義的 model 進行比較。

因為我們的 model._id1,它和 manufacturers 數組的第一項元素的 _id 一致,所以我們返回的兩個 option 標籤,第一個 selected 屬性為 true,第二個為 false

模板語法:條件選擇

上面的講述了循環是如何在 Vue 中使用的,下面我們來看一看條件語法是如何在 Vue 中使用的:

<span v-if="isEditing">Update Product</span>
<span v-else>Add Product</span>
<script>
export default {
data: { isEditing: false },
}
</script>

我們可以看到,通過在標籤上加 v-if 並後面緊跟加 v-else 的標籤我們可以判斷最終渲染的標籤,比如我們這裡 isEditingfalse,那麼我們最終渲染的結果為:

<span>Add Product</span>

當然你可以添加諸如 v-else-if 的標籤來做多重判斷。

提示

這裡的帶 v-ifv-else-ifv-else 的標籤需要依次緊跟著前面的標籤,不能在這些帶條件屬性的標籤中插入其他不帶條件的標籤,比如下面這段代碼就是錯誤的:

<span v-if="isEditing">Update Product</span>
<span>我是錯誤插入的標籤</span>
<span v-else>Add Product</span>
<script>
export default {
data: { isEditing: false },
}
</script>

動手實現

講解完 Vue 的基礎知識之後,我們馬上將所有的知識運用起來,來編寫我們的 ProductForm.vue 組件,它用來添加或者更新商品的信息。

我們在 src/components 中創建 ProductForm.vue 表單組件,代碼如下:

<template>
<form @submit.prevent="saveProduct">
<div class="col-lg-5 col-md-5 col-sm-12 col-xs-12">
<div class="form-group">
<label>Name</label>
<input
type="text"
placeholder="Name"
v-model="model.name"
name="name"
class="form-control" />
</div>
<div class="form-group">
<label>Price</label>
<input
type="number"
class="form-control"
placeholder="Price"
v-model="model.price"
name="price" />
</div>
<div class="form-group">
<label>Manufacturer</label>
<select
type="text"
class="form-control"
v-model="model.manufacturer"
name="manufacturer">
<template v-for="manufacturer in manufacturers">
<option :value="manufacturer._id" :selected="manufacturer._id == (model.manufacturer && model.manufacturer._id)">{{manufacturer.name}}</option>
</template>
</select>
</div>
</div>
<div class="col-lg-4 col-md-4 col-sm-12 col-xs-12">
<div class="form-group">
<label>Image</label>
<input
type="text"
lass="form-control"
placeholder="Image"
v-model="model.image"
name="image"
class="form-control" />
</div>
<div class="form-group">
<label>Description</label>
<textarea
class="form-control"
placeholder="Description"
rows="5"
v-model="model.description"
name="description"
></textarea>
</div>
<div class="form-group new-button">
<button class="button">
<i class="fa fa-pencil"></i>
<!-- Conditional rendering for input text -->
<span v-if="isEditing">Update Product</span>
<span v-else>Add Product</span>
</button>
</div>
</div>
</form>
</template>
<script>
export default {
props: ['model', 'manufacturers', 'isEditing'],
methods: {
saveProduct() {
this.$emit('save-product', this.model)
}
}
}
</script>

這段代碼看起來很長,你可能被嚇到了,讓我們一段一段來拆解它。

script 部分

這裡我們的 props 接收來自父組件的三個參數:modelmanufacturersisEditing

然後我們定義了一個 saveProduct 方法,就是當用戶填寫完商品信息的表單之後,點擊提交按鈕會觸發的方法,在 saveProduct 內部,我們調用了父組件的 save-product 方法,並把修改後的 this.model 變量內容傳給父組件。所以這裡我們還可以看到,methods 不僅可以使得數據可以雙向流動,而且還可以在子組件反向操作父組件的內容,使得數據還可以上下流動。

template 部分

接下來我們再來談一談 template 裡面發生的事情。

可以看到 template 裡面就是一個表單,這個表單定義了一個 submit 事件,並且使用了禁用默認事件的簡潔寫法 @submit.prevent。 這個事件觸發會調用我們上面提到的 saveProduct 方法。

接著我們定義了好幾個 classform-group 的元素塊,每個塊代表我們創建商品所需要填寫的相關信息,我們注意到,前兩個 form-group 使用 v-model 雙向綁定語法分別綁定了 modelnameprice 屬性。

第三個 form-group 我們首先在 select 標籤中使用 v-model 雙向綁定了 model.manufacturer,表示我們在視圖裡面進行選擇時,會修改對應的 model.manufacturer 屬性。

接著我們對 manufacturers 進行循環遍歷,構造多個 option 標籤選項,然後使用了屬性綁定語法的簡潔寫法綁定了 optionvalueselected 屬性,value 屬性賦值為 manufacturer._idselected 屬性會進行判斷,model.manufacturer && model.manufacturer._id 表示首先檢驗 modelmanufacturer 屬性是否存在,正常情況下它應該是一個對象,如果 model.manufacturer 屬性存在,那麼獲取 model.manufacturer._id,然後用獲取到的這個 model.manufacturer._idmanufacturer._id 進行比較,如果一致,那麼 selected 屬性為 true,不一致就為 false

然後我們來看一下第二段 form-group,也就是第 4-6 個 form-group

可以看到前兩個 form-group 使用 v-model 雙向綁定了 model.imagemodel.description ,表示當用戶上傳了商品圖片和描述之後,對應的 model.image 就會變成用戶上傳的商品圖片,model.description 就會變成用戶撰寫的商品描述。

最後一個 form-group 我們使用了條件選擇語法,判斷 isEditing,來渲染不同的按鈕文案。

Vue 組件組合

編寫完上面的表單之後,我們在 New.vue 中引入我們創建的表單組件:

<template>
<product-form
@save-product="addProduct"
:model="model"
:manufacturers="manufacturers"
>
</product-form>
</template>
<script>
import ProductForm from '@/components/products/ProductForm.vue';
export default {
data() {
return {
model: {},
manufacturers: [
{
_id: 'sam',
name: 'Samsung',
},
{
_id: 'apple',
name: 'Apple',
},
],
};
},
methods: {
addProduct(model) {
console.log('model', model);
},
},
components: {
'product-form': ProductForm
}
}
</script>

當一個組件要在模板語法中使用另外一個組件時,需要申明此組件,即在組件的 components 屬性中申明要使用的組件,比如我們上面使用名為 'product-form' 的名稱來申明使用 ProductForm 組件,這樣在 template 中我們就可以以 <product-form /> 形式使用導入的表單組件。

同時我們在組件的 data 中定義了 modelmanufacturers 以及在 methods 中定義了 addProduct 方法,並將它們以屬性綁定 :model="model":manufacturers="manufacturers" 和事件綁定 @save-product="saveProduct" 的方式傳遞給表單組件。

當保存上面編寫的代碼之後,我們打開瀏覽器,點擊導航鏈接 Admin,然後點擊子導航鏈接 New Products,切換到我們的 New.vue 添加商品頁面,我們可以看到如下的效果:

從零到部署:用Vue和Express實現迷你全棧電商應用(三)

小結

到現在為止,我們已經瞭解了 Vue 的基礎部分,包括:

  • 用路由進行多頁面的跳轉和導航
  • 用嵌套路由和動態路由合理組織頁面
  • Vue 組件和 Vue 實例
  • Vue 模板語法

掌握了這些知識後,我們已經可以實現很多前端的功能,完成一些簡單的 Vue 應用了。但是如果要完成數據邏輯複雜的大型應用,目前學到的知識就力不從心了。但是沒關係,我們將在後面學習 Vuex 這一前端狀態管理工具,有了 Vuex 的加持,我們就能用 Vuex 寫出任意複雜的應用了。

想要學習更多精彩的實戰技術教程?來圖雀社區逛逛吧。

從零到部署:用Vue和Express實現迷你全棧電商應用(三)

相關文章

雪花算法Snowflake&Sonyflake

Vue解析剪切板圖片並實現發送功能

深挖蘋果審核規則,提升iOS審核通過率

從網絡科學的視角,入門圖網絡