上一篇 《没时间学 Vue (13) —— 绑定(五):计算属性和侦听器》中 “乱入” 讲了计算属性和侦听器,本篇咱们接着《没时间学 Vue (12) —— 组件(二):组件的创建、使用和数据传递》,继续讲组件的数据传递。
我们前篇的例子中,组件本身只有展示功能,而没有编辑功能 —— 也就是没有各种 的功能。
实际的项目中会用到这类组件,但也有不少情况下会用到带编辑功能的组件 —— 比如说,基于某些数据字典(Master)数据的选择组件(如:权限、用户类型、或者其他的某业务数据类型等),也可能是点赞、打分这种点击后执行某种业务逻辑的组件。
我们先从模拟选择地理位置的组件开始入手。为了简化模型,我们只做选择省级行政区的组件。
民政部对县级和以上的行政区划有标准编码(http://www.mca.gov.cn/article/sj/xzqh/2020/),咱们从中选择省级行政区的即可。比如:
行政区划代码 | 地名 |
---|---|
110000 | 北京市 |
120000 | 天津市 |
130000 | 河北省 |
140000 | 山西省 |
150000 | 内蒙古自治区 |
我们还是写好省级行政区的组件,然后在 App.vue 中使用和测试。
根据前两篇的内容,我们很快就能写出 “第一版” 来,大概是这样的。
省份选择组件 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 中的值并没有跟着改变 …
如果你对之前的章节还有印象,那么你很容易能推测出来:
接着上面的原因分析,我们很容易想到:
既然 v-bind 是单向绑定的,那我们用双向绑定的 v-model 就好了。
于是我们像下面这样修改了代码:
结果发现画面显示反而更糟糕了
不要吃惊,其实 Vue 官网(《表单输入绑定 - 基础用法》)上已经说了,
你可以用 v-model 指令在表单 、
那我们自己写的组件里能不能用 v-model 呢?
可以,不过得遵循 《组件基础 - 在组件上使用 v-model》中的规则。
其中给出的实例代码如下:
Vue.component('custom-input', {
props: ['value'],
template: `
`
})
我们可以类比着如下修改我们的 Location.vue:
检验一下结果,数据修改后可以正确更新 App.vue 了,但是初始显示不正确 …
虽然参照了 Vue 官网的做法,还是吃了个瘪。官网上正确的的做法在《自定义事件 - 自定义组件的 v-model》,还需要在组件中加一个 model 属性。
咱们只要在 Location.vue 中照葫芦画瓢加上 model 属性,初始显示就正常了。
model 属性的详细说明在 https://cn.vuejs.org/v2/api/#model ,里面涉及的用法更加的绕。
https://segmentfault.com/a/1190000019337976 这篇倒是写得比较清楚,有时间可以翻一翻。
除了上面的 v-model 语法糖,Vue 官网的《组件基础 - 使用事件抛出一个值》中还推荐了使用 $emit 触发一个自定义事件、并且在 $emit 的第二个参数中传递数据的做法。
我们也可以类似地改造之前的代码 —— 记得先回退到第一版 —— 要是你有使用 git 的好习惯,这个事情就很容易做了
Location.vue :
App.vue:
虽然代码比 v-model 多了一丢丢,但是效果是杠杠的,理解起来也很容易。
再回过头来看一下上面说的 v-model 语法糖,其实也是用了 $emit 的!
只不过,触发的事件不是自定义的,而是 input 这种。
你要是觉得上面第三版 $emit 的代码写得有点儿多的话,
Vue 还提供了 v-bind.sync 这种简化的写法,
来省去 App.vue 中的事件处理函数。
不过,这个也是有前提条件的,只有符合特定的写法才能生效。
1)Location.vue 中仍然要用 $emit 触发事件;
2) $emit 触发的事件的名称必须是 update:绑定属性名
这种风格的;
3) $emit 的第二个参数,必须是绑定属性的新的值。
这种做法很少见于 “组件间数据传递” 的常规套路,但是在各种业务处理中却很常见。
比如说,Java 和 C# 中的排序回调,JavaScript 和 Python 的高阶函数等等。
它的出发点跟 $emit 刚好相反,
使用者不捕获组件中抛出的事件 —— 组件中直接调用使用者传递过来的回调函数。
其结果也是 OK 的 —— 这就是第一性原理。
1)将上面的控件变成 checkbox 的风格
2)保留控件的下拉框风格,但是在 App.vue 中同时显示代码和地名: