最近我们的设计师对先前的系统进行了大量的重构。加了大量的 SelectList 的界面。所谓的“SelectList”,就是有选择状态的列表。点击选择某项可以获取该项的值。
自定义原因
队里的有同学说可以使用 Element 中竖版单选框组组件或标签页组件。有现成的当然拿来用就好好了,但我们仔细对比了一下设计师的高保真设计稿后,发现这两个组件和设计稿还是差得比较多,需要覆写大量样式,改起来是有一点难度的。最总要的不是样式不太好改,而且是因为两个组件的当前选择项对应的数据类型只能 string
、number
、 boolean
之类的基本数据类型。我们的需求师能支持对象。好吧,自定义 SelectList 组件吧。
定义 props
props 就是组件对外的接口。props 定义得适当,可以最大限度地重用代码。根据我们的具体需求,我们定义了以下 props:
参数 | 说明 | 类型 | 默认值 | requied |
---|---|---|---|---|
valueField | 值字段 | String | value | false |
labelField | biao'qian字段 | String | label | false |
value | 当前选择值 | [Object, String] | {} | false |
list | 列表数据源 | Array | [] | true |
注意,只有 list
prop(参数) 是必需的。没有该 prop,就不会渲染出列表。
新建 SelectList
组件,props 对应的代码如下:
props: {
valueField: {
type: String,
default: "value",
},
labelField: {
type: String,
default: "label",
},
value: {
type: [Object, String],
default: () => {},
},
list: {
type: Array,
required: true,
default: () => [],
}
},
接下来就用定义好的 props 来渲染列表。
渲染列表
比较简单,我们可以用 v-for 指令基于 list
数组来渲染一个 ul
列表。v-for 指令需要使用 item in items 形式的特殊语法,每个 item 对应被迭代的数组元素,再界面上显示为一个 li
元素,元素值根据数组元素值得类型来决定。如果是对象,则取item[labelField];否则直接取item。
在 SelectList
组件的模板(template)中,添加如下代码:
<ul class="select-list">
<li
v-for="(item, index) in list"
:key="index"
:class="{ selected: value === item }"
>
{{ item[labelField] || item }}
</li>
</ul>
另外,当迭代值 item
与当前选择值 value
相等时,为列表添加选中状态样式 selected
。样式 selected
我们放到后面定义。
注意,为了更好维护子组件状态,使用 v-for 指令时,需要为每项提供一个唯一 key
。
测试渲染
下面我们来测试一下看渲染是否正确,props有没有为题。
- Mock 数据
在 App
组件中 Mock 一个数组,数据结构如下:
const list1 = [
{
value: "1",
title: "React",
},
{
value: "2",
title: "Vue",
},
{
value: "3",
title: "Angular",
},
{
value: "4",
title: "React Native",
},
{
value: "5",
title: "Weex",
},
];
然后将其添加到 data
选项中使其可以具有响应式。
- 添加实例
如下在 App
组件中添加 SelectList
组件实例。然后为 list
prop 绑定 list1
,为 label-field
指定定 title
属性名`。
<select-list :list="list1" label-field="title" />
现在列表时有了,但不能交互。下面来看看该组件的事件处理。
定义事件
也就是说既要在初始化初始化时。能给当前选项当前选择 value
赋值,又能在用户选择某一个选项时,将当前选择值,向外传递到父组件作用域当中,以便组件的实例能够访问。换句话说说,就是要实现。呃,当前值的双向数据绑定。当然,我们可以。在props中再加一个 Onselect之类的参数值。通过该回调参数来访问当前的value。这有点react的味道。但 Vue 就是 Vue,作为 Vue 的最佳实践,我们应该用emit 来一个事件当前选择值。当然,能实现自定义 v-model 就最好不过了。
还记得Vue 双向数据绑定最简单的模式吗?那就是自定义 value
Prop,派发自定义 input
事件。
在 SelectList
组件渲染列表中添加如下代码:
@click="$emit('input', item)"
实现当前选择值 value
的双向数据绑定 ,就这么简单!可以我们来测试一下事件。
测试渲染事件
- 添加测试数据
在 App
组件中 Mock 一个对象,让它等于 list1
数组的第二个元素:
value1: list1[1],
- 使用自定义 v-model
在 App
组件中为 value
prop 双向绑定 value1
,并添加 SelectList
组件实例:
<select-list v-model="value1" :list="list1" label-field="title" />
- 绑定模板
在 App
组件的模板中添加:
<pre>{{ value1 }}</pre>
可以看到当用户点击列表中的某一项时,绑定的 value1
将跟随改变。但我们初始化 value1
为数组的第二个元素,照理页面加载完列表就该选中列表中的第二项。实际上第二项已经添加了选中状态,UI上未体现出来是应为我们还没有添加相应的样式。如果使用 Chrome 浏览器,可以通过右键选中第二项,点击“检查”,可以看到该 li
上有一个 selected
样式类。
添加样式
这个组件的功能基本上开发好了,现在我们来添加样式。
在 SelectList
组件的样式(style)块中添加如下样式:
@border: "#ddd";
ul.select-list {
list-style: none;
margin: 0;
padding-left: 0;
border: 1px solid #ddd;
li {
padding: 6px 12px;
border-bottom: 1px solid #ddd;
box-sizing: border-box;
font-size: 14px;
cursor: pointer;
&:last-child {
border-bottom: none;
}
&:hover {
background-color: #f5f7fa;
}
&.selected {
padding-left: 10px;
background-color: #f0f7ff;
box-shadow: none;
border-left: 2px solid #00bbff;
}
}
}
最终效果如下图:
对于要复用的组件来说,为style
添加scoped
(组件作用域)是个好注意。
关于测试
为了让测试更有说服力,我们需要再 Mock 一个元素为字符串的数组来测试一下。当然如果能写用单元测试,那就更好。
和 Vue 的“渐进”一样,我们的测试也是循序渐进的——实现一个小目标就及时测试一下——这样做开发测试过程遇到 Bug 就可以马上改。当然如果会 TDD,那就更好。
参考链接
- 访问 codesandbox 查看项目代码。
- Vue 双向数据绑定
评论 (0)