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

tuture發表於2020-04-28

這篇文章中,我們將講解 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 實現迷你全棧電商應用(三)

相關文章