上一篇 《没时间学 Vue (13) —— 绑定(五):计算属性和侦听器》中 “乱入” 讲了计算属性和侦听器,本篇咱们接着《没时间学 Vue (12) —— 组件(二):组件的创建、使用和数据传递》,继续讲组件的数据传递。
本篇的主要内容如下
1、组件把数据传递给使用者
我们前篇的例子中,组件本身只有展示功能,而没有编辑功能 —— 也就是没有各种 <input> 的功能。
实际的项目中会用到这类组件,但也有不少情况下会用到带编辑功能的组件 —— 比如说,基于某些数据字典(Master)数据的选择组件(如:权限、用户类型、或者其他的某业务数据类型等),也可能是点赞、打分这种点击后执行某种业务逻辑的组件。
我们先从模拟选择地理位置的组件开始入手。为了简化模型,我们只做选择省级行政区的组件。
民政部对县级和以上的行政区划有标准编码(http://www.mca.gov.cn/article/sj/xzqh/2020/),咱们从中选择省级行政区的即可。比如:
| 行政区划代码 | 地名 |
|---|---|
| 110000 | 北京市 |
| 120000 | 天津市 |
| 130000 | 河北省 |
| 140000 | 山西省 |
| 150000 | 内蒙古自治区 |
我们还是写好省级行政区的组件,然后在 App.vue 中使用和测试。

2、 第一版(v-bind 版,无法更新)
根据前两篇的内容,我们很快就能写出 “第一版” 来,大概是这样的。
省份选择组件 Location.vue:
<template>
<span class="location">
<select v-model="selected">
<option
v-for="location in locations"
:key="location.code"
:value="location.code"
>{{ location.name }}</option
>
</select>
</span>
</template>
<script>
export default {
name: "Location",
props: {
selected: Number,
},
data: function() {
return {
locations: [
{ code: 110000, name: "北京市" },
{ code: 120000, name: "天津市" },
{ code: 130000, name: "河北省" },
{ code: 140000, name: "山西省" },
]
};
}
};
</script>
<style></style>
App.vue
<template>
<div id="app">
<div>请选择位置:<Location :selected="location"></Location></div>
<div>您选择的位置是:{{ location }}</div>
</div>
</template>
<script>
import Location from "./components/Location";
export default {
name: "App",
components: {
Location,
},
data: function() {
return {
location: 110000,
};
},
};
</script>
<!-- 没有特殊的样式 -->
<style></style>
验证一下效果。初始显示 OK!
但是,修改位置之后,App.vue 中的值并没有跟着改变 …

如果你对之前的章节还有印象,那么你很容易能推测出来:
- 《没时间学 Vue (12) —— 组件(二):组件的创建、使用和数据传递》
props 是用来单向接收来自使用者指定的数据的(参见:单向数据流) - 《没时间学 Vue (3) —— 绑定(二):v-bind 和 v-model》
v-bind 的数据是单向绑定的,因此无法更新 App.vue 中的 location 数据。
3、第二版 (v-model 版,好大一颗语法糖)
接着上面的原因分析,我们很容易想到:
既然 v-bind 是单向绑定的,那我们用双向绑定的 v-model 就好了。
于是我们像下面这样修改了代码:
结果发现画面显示反而更糟糕了 😳😳😳

不要吃惊,其实 Vue 官网(《表单输入绑定 - 基础用法》)上已经说了,
你可以用 v-model 指令在表单 <input>、<textarea> 及 <select> 元素上创建双向数据绑定。
它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但v-model 本质上不过是语法糖。
它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
那我们自己写的组件里能不能用 v-model 呢?
可以,不过得遵循 《组件基础 - 在组件上使用 v-model》中的规则。
其中给出的实例代码如下:
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
我们可以类比着如下修改我们的 Location.vue:

检验一下结果,数据修改后可以正确更新 App.vue 了,但是初始显示不正确 …

虽然参照了 Vue 官网的做法,还是吃了个瘪。官网上正确的的做法在《自定义事件 - 自定义组件的 v-model》,还需要在组件中加一个 model 属性。

咱们只要在 Location.vue 中照葫芦画瓢加上 model 属性,初始显示就正常了。

model 属性的详细说明在 https://cn.vuejs.org/v2/api/#model ,里面涉及的用法更加的绕。
https://segmentfault.com/a/1190000019337976 这篇倒是写得比较清楚,有时间可以翻一翻。
4、第三版($emit 自定义事件)
除了上面的 v-model 语法糖,Vue 官网的《组件基础 - 使用事件抛出一个值》中还推荐了使用 $emit 触发一个自定义事件、并且在 $emit 的第二个参数中传递数据的做法。

我们也可以类似地改造之前的代码 —— 记得先回退到第一版 —— 要是你有使用 git 的好习惯,这个事情就很容易做了 😀
Location.vue :

App.vue:

虽然代码比 v-model 多了一丢丢,但是效果是杠杠的,理解起来也很容易。
再回过头来看一下上面说的 v-model 语法糖,其实也是用了 $emit 的!
只不过,触发的事件不是自定义的,而是 input 这种。
5、第四版 ($emit + v-bind.sync)
你要是觉得上面第三版 $emit 的代码写得有点儿多的话,
Vue 还提供了 v-bind.sync 这种简化的写法,
来省去 App.vue 中的事件处理函数。

不过,这个也是有前提条件的,只有符合特定的写法才能生效。
1)Location.vue 中仍然要用 $emit 触发事件;
2) $emit 触发的事件的名称必须是 update:绑定属性名 这种风格的;
3) $emit 的第二个参数,必须是绑定属性的新的值。
6、第五版(回调函数)
这种做法很少见于 “组件间数据传递” 的常规套路,但是在各种业务处理中却很常见。
比如说,Java 和 C# 中的排序回调,JavaScript 和 Python 的高阶函数等等。
它的出发点跟 $emit 刚好相反,
使用者不捕获组件中抛出的事件 —— 组件中直接调用使用者传递过来的回调函数。


其结果也是 OK 的 —— 这就是第一性原理。
7、思考题
1)将上面的控件变成 checkbox 的风格

2)保留控件的下拉框风格,但是在 App.vue 中同时显示代码和地名:

本文深入探讨了Vue组件间的数据传递,包括v-model双向绑定、$emit自定义事件与回调函数的使用,通过实例演示如何解决不同场景下的组件数据同步问题。
533

被折叠的 条评论
为什么被折叠?



