这篇文章中,我们将讲解 Vue 实例的 Props 和 Methods,接着我们又讲解了最常见的 Vue 模板语法,并通过实例的方式将这些模板语法都实践了一番,最后我们讲解了 Vue 组件的组合,并完成了我们的发表商品页面。
欢迎阅读《从零到部署:用 Vue 和 Express 实现迷你全栈电商应用》系列:
如果您觉得我们写得还不错,记得 点赞 + 关注 + 评论 三连,鼓励我们写出更好的教程💪
当我们完成了商城应用的基本页面框架之后,我们就可以开始考虑具体页面的内容了。首先我们要考虑的就是数据的来源,即添加商品页面。有了添加商品的入口,我们就可以展示商品列表,获取商品详情,甚至是修改商品信息。
不过在此之前,我们打算先复习一下 Vue 的一些重要知识点。如果你已经很熟悉了,可以直接跳到下面实现 ProductForm.vue 的代码部分。
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
是用来定义在组件中会用到的一些方法,如果说我们前面提到的 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-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 -
我们已经看到在 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>
前面我们提到通过 {{}}
插值语法渲染来自 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="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>
这段代码看起来很长,你可能被吓到了,让我们一段一段来拆解它。
这里我们的 props
接收来自父组件的三个参数:model
、manufacturers
、isEditing
。
然后我们定义了一个 saveProduct
方法,就是当用户填写完商品信息的表单之后,点击提交按钮会触发的方法,在 saveProduct
内部,我们调用了父组件的 save-product
方法,并把修改后的 this.model
变量内容传给父组件。所以这里我们还可以看到,methods
不仅可以使得数据可以双向流动,而且还可以在子组件反向操作父组件的内容,使得数据还可以上下流动。
接下来我们再来谈一谈 template
里面发生的事情。
可以看到 template
里面就是一个表单,这个表单定义了一个 submit
事件,并且使用了禁用默认事件的简洁写法 @submit.prevent
。 这个事件触发会调用我们上面提到的 saveProduct
方法。
接着我们定义了好几个 class
为 form-group
的元素块,每个块代表我们创建商品所需要填写的相关信息,我们注意到,前两个 form-group
使用 v-model
双向绑定语法分别绑定了 model
的 name
和 price
属性。
第三个 form-group
我们首先在 select
标签中使用 v-model
双向绑定了 model.manufacturer
,表示我们在视图里面进行选择时,会修改对应的 model.manufacturer
属性。
接着我们对 manufacturers
进行循环遍历,构造多个 option
标签选项,然后使用了属性绑定语法的简洁写法绑定了 option
的 value
和 selected
属性,value
属性赋值为 manufacturer._id
,selected
属性会进行判断,model.manufacturer && model.manufacturer._id
表示首先检验 model
的 manufacturer
属性是否存在,正常情况下它应该是一个对象,如果 model.manufacturer
属性存在,那么获取 model.manufacturer._id
,然后用获取到的这个 model.manufacturer._id
和 manufacturer._id
进行比较,如果一致,那么 selected
属性为 true
,不一致就为 false
。
然后我们来看一下第二段 form-group
,也就是第 4-6 个 form-group
。
可以看到前两个 form-group
使用 v-model
双向绑定了 model.image
和 model.description
,表示当用户上传了商品图片和描述之后,对应的 model.image
就会变成用户上传的商品图片,model.description
就会变成用户撰写的商品描述。
最后一个 form-group
我们使用了条件选择语法,判断 isEditing
,来渲染不同的按钮文案。
编写完上面的表单之后,我们在 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
中定义了 model
和 manufacturers
以及在 methods
中定义了 addProduct
方法,并将它们以属性绑定 :model="model"
、:manufacturers="manufacturers"
和事件绑定 @save-product="saveProduct"
的方式传递给表单组件。
当保存上面编写的代码之后,我们打开浏览器,点击导航链接 Admin
,然后点击子导航链接 New Products
,切换到我们的 New.vue
添加商品页面,我们可以看到如下的效果:
到现在为止,我们已经了解了 Vue 的基础部分,包括:
掌握了这些知识后,我们已经可以实现很多前端的功能,完成一些简单的 Vue 应用了。但是如果要完成数据逻辑复杂的大型应用,目前学到的知识就力不从心了。但是没关系,我们将在后面学习 Vuex 这一前端状态管理工具,有了 Vuex 的加持,我们就能用 Vuex 写出任意复杂的应用了。
想要学习更多精彩的实战技术教程?来图雀社区逛逛吧。