使用 Vue 自定义 SelectList 组件

使用 Vue 自定义 SelectList 组件

Flying
2020-04-26 / 0 评论 / 99 阅读 / 正在检测是否收录...

最近我们的设计师对先前的系统进行了大量的重构。加了大量的 SelectList 的界面。所谓的“SelectList”,就是有选择状态的列表。点击选择某项可以获取该项的值。

自定义原因

队里的有同学说可以使用 Element 中竖版单选框组组件或标签页组件。有现成的当然拿来用就好好了,但我们仔细对比了一下设计师的高保真设计稿后,发现这两个组件和设计稿还是差得比较多,需要覆写大量样式,改起来是有一点难度的。最总要的不是样式不太好改,而且是因为两个组件的当前选择项对应的数据类型只能 stringnumberboolean之类的基本数据类型。我们的需求师能支持对象。好吧,自定义 SelectList 组件吧。

定义 props

props 就是组件对外的接口。props 定义得适当,可以最大限度地重用代码。根据我们的具体需求,我们定义了以下 props:

参数说明类型默认值requied
valueField值字段Stringvaluefalse
labelFieldbiao'qian字段Stringlabelfalse
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有没有为题。

  1. 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 选项中使其可以具有响应式。

  1. 添加实例

如下在 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 的双向数据绑定 ,就这么简单!可以我们来测试一下事件。

测试渲染事件

  1. 添加测试数据

App 组件中 Mock 一个对象,让它等于 list1 数组的第二个元素:

value1: list1[1],
  1. 使用自定义 v-model

App 组件中为 value prop 双向绑定 value1,并添加 SelectList 组件实例:

<select-list v-model="value1" :list="list1" label-field="title" />
  1. 绑定模板

App 组件的模板中添加:

<pre>{{ value1 }}</pre>

可以看到当用户点击列表中的某一项时,绑定的 value1 将跟随改变。但我们初始化 value1 为数组的第二个元素,照理页面加载完列表就该选中列表中的第二项。实际上第二项已经添加了选中状态,UI上未体现出来是应为我们还没有添加相应的样式。如果使用 Chrome 浏览器,可以通过右键选中第二项,点击“检查”,可以看到该 li 上有一个 selected 样式类。

selected-class.png

添加样式

这个组件的功能基本上开发好了,现在我们来添加样式。

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;
    }
  }
}

最终效果如下图:

select-list.png

对于要复用的组件来说,为 style 添加 scoped (组件作用域)是个好注意。

关于测试

为了让测试更有说服力,我们需要再 Mock 一个元素为字符串的数组来测试一下。当然如果能写用单元测试,那就更好。

和 Vue 的“渐进”一样,我们的测试也是循序渐进的——实现一个小目标就及时测试一下——这样做开发测试过程遇到 Bug 就可以马上改。当然如果会 TDD,那就更好。

参考链接

1

评论 (0)

取消