这篇文章中,我们将讲解 Vue 实例的 Props 和 Methods,接着我们又讲解了最常见的 Vue 模板语法,并通过实例的方式将这些模板语法都实践了一番,最后我们讲解了 Vue 组件的组合,并完成了我们的发表商品页面。
欢迎阅读《从零到部署:用 Vue 和 Express 实现迷你全栈电商应用》系列:
- 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(一)
- 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(二)
- 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(三)(也就是这篇)
用模板语法和双向绑定实现数据的添加
当我们完成了商城应用的基本页面框架之后,我们就可以开始考虑具体页面的内容了。首先我们要考虑的就是数据的来源,即添加商品页面。有了添加商品的入口,我们就可以展示商品列表,获取商品详情,甚至是修改商品信息。
不过在此之前,我们打算先复习一下 Vue 的一些重要知识点。如果你已经很熟悉了,可以直接跳到下面实现 ProductForm.vue 的代码部分。
Vue 实例:Props 和 Methods
Props
props
是 Vue 进行组件之间传参的形式。比如我们有如两个组件
New.vue
和
ProductForm.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.vue
的
script
部分导出的对象里面找到
props
属性,然后取到
manufacturers
属性。
Methods
然后是
methods
,
methods
是用来定义在组件中会用到的一些方法,如果说我们前面提到的
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 属性,如
id
、
class
等,是不是也能动态的获取变化值,你还别说,还真的可以,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
中,定义了
_id
和
value
值,然后我们通过在
<template>
模板中使用
v-bind
语法动态的给
option
标签的
id
和
value
属性赋值,最后的结果看起来是这样的:
<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 实例中的
model
的
name
属性进行了双向绑定,即当 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._id
为
1
,它和
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
的标签我们可以判断最终渲染的标签,比如我们这里
isEditing
为
false
,那么我们最终渲染的结果为:
<span>Add Product</span>
复制代码
当然你可以添加诸如
v-else-if
的标签来做多重判断。
提示
这里的带
v-if
、v-else-if
或v-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