专栏名称: 京东设计中心JDC
专业,创造力,激情,设计。京东用户体验设计部门,致力于创造更美好的电子商务购物体验。
目录
相关文章推荐
庞门正道  ·  准备好上班了吗? ·  2 天前  
ZaomeDesign  ·  每日灵感丨二月五日 ·  昨天  
庞门正道  ·  21张剪纸,剪出来的全是震撼~ ·  5 天前  
字体设计  ·  招福万来,夜间飞行 ·  1 周前  
国际家居  ·  比利时极简设计,朴实无华 ·  4 天前  
国际家居  ·  比利时极简设计,朴实无华 ·  4 天前  
51好读  ›  专栏  ›  京东设计中心JDC

JDC丨京东设计中心 - [译文]使用Vue.js创建自定义输入框

京东设计中心JDC  · 公众号  ·  · 2017-09-28 17:44

正文

原文: https://www.smashingmagazine.com/2017/08/creating-custom-inputs-vue-js/

基于组件的库或者框架(例如 Vue)给我们在开发 可复用的组件 带来了极大的方便。这些组件可以非常简单的在独立的应用程序中被使用,并且最终呈现的效果一致。

比如,在表单输入的场景,在功能上往往会比较复杂,通常我们的做法是希望使用组件将表单中 自定义的设计 、标签、验证、帮助信息等进行封装,以确保这些能被依次正确的渲染。

Vue 有一个特别且重要的指令 v-model,它通过绑定和捕获输入事件来实现数据的双向绑定。如果你要构建自定义输入组件,那么该组件毫无疑问的需要支持 v-model 指令了。

遗憾的是,当我查阅 Vue 单选按钮或者复选框的自定义的示例时,发现他们要么没有考虑到 v-model 指令,要么没有正确的实现。有一些自定义输入框的 使用文档 ,但它没有解释自定义单选按钮和复选框如何使用,我将在下面给出一些例子和说明。

本文意在帮助你理解以下几点内容:

  1. 理解 v-model 指令在原生输入框上是如何实现的,主要侧重于单选按钮和复选框。

  2. 理解 v-model 指令一般在自定义组件是如何实现的。

  3. 学习如何创建具有类似 v-model 指令功能的自定义单选按钮和复选框。

本文涉及到的代码示例,我将采用ES2015+。在使用 vue.component 或者 new Vue 来编写代码时,我更倾向于使用 单文件组件 的语法,这样能使项目结构更为清晰。

v-model通常是如何工作的?

官方的 vue 文档 在这个主题上已经讲的很清晰了,但是仍然有一些遗漏点。总之,我都会从头讲解这块的内容。v-model 实质上只是为我们提供双向数据绑定功能的语法糖。其会随着不同类型的表单控件而不同。具体例子如下:

1

2

3

4

5

6

< input v - model = "message" placeholder = "edit me" >

< p > Message : {{ message }} p >

< p > message : p >

< p style = "white-space: pre-line" > {{ message }} p >

< textarea v - model = "message" placeholder = "add multiple lines" > textarea >


当使用文本输入框(包括 email,number 类型等)或 textarea 类型时,
v-model ="varName" 等同于 :value ="varName" @input ="e => varName = e.target.value" 。这意味着每次在文本框输入时,input 事件通过输入事件将 value 值传给 varName,每次输入时使用这种方式更新 varName。同样,除了含有 multiple 属性的 select 元素,普通的 select 也是这种实现方式。

单选按钮(Radio Buttons)

那么单选按钮是怎么实现的呢?

1

2

3

< input type = "radio" value = "One" v - model = "picked" >

< input type = "radio" value = "Two" v - model = "picked" >

< span > Picked : {{ picked }} span >


相当于如下所示的代码:

1

2

3

< input type = "radio" value = "One" : checked = "picked == 'One'" @ change = "picked = $event.target.value" >

< input type = "radio" value = "Two" : checked = "picked == 'Two'" @ change = "picked = $event.target.value" >

< span > Picked : {{ picked }} span >

你会发现 v-model 跟 value 值没有直接关系。但是它依然在 change 事件做了同样的事情(尽管事件由 input 变成了 change)。然后根据 picked 的值是否与该单选按钮的值相同,确定单选按钮是否被选中。

多选框(Checkboxes)

多选框讨论起来就有点复杂了。因为它有两种不同的行为表现,这取决于只有一个单独的 checkbox 绑定了 v-model 指令,还是多个都绑定了 v-model 指令。

如果你使用单个复选框,则 v-model 会将其视为布尔值,并忽略该值。

1

< input type = "checkbox" value = "foo" v - model = "isChecked" >


等同于如下所示的代码:

1

< input type = "checkbox" value = "foo" : checked = "isChecked" @ change = "isChecked = $event.target.value" >


如果你希望它可以不仅仅表示 true 和 false 的话,可以使用 true-value 和 false-value 属性来设置复选框被选中和未选中的值。

1

< input type = "checkbox" value = "foo" v - model = "isChecked" true - value = "1" false - value = "0" >


等同于如下所示的代码:

1

< input type = "checkbox" value = "foo" : checked = "isChecked == '1'" @ change = "isChecked = $event.target.checked ? '1' : '0'" >


这是单一复选框的例子。如果你有多个复选框共享一个模型,那么这些复选框将填充一个由所有复选框被选中的 value 值所组成的数组。同时必须确保你传递的模型是数组类型,否则会产生一些奇怪的问题。当然,true-value 和 false-value 在此场景下,将不起任何作用了。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

< template >

< div >

< input type = "checkbox" value = "foo" v - model = "checkedVals" >

< input type = "checkbox" value = "bar" v - model = "checkedVals" >

< input type = "checkbox" value = "baz" v - model = "checkedVals" >

div >

template >


很难在上面的代码通过元素属性添加方法来实现 v-model,所以我将部分逻辑转移到组件的方法上。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

<template>

  <div>

    <input type="checkbox" value="foo" v-model="checkedVals">

    <input type="checkbox" value="bar" v-model="checkedVals">

    <input type="checkbox" value="baz" v-model="checkedVals">

  div>

template>

: ['bar'] }

    },

    methods: {

      shouldBeChecked(val) {

        return this.checkedVals.includes(val)

      },

      updateVals(e) {

        let isChecked = e.target.checked

        let val = e.target.value

 

        if (isChecked) {

          this.checkedVals.push(val)

        } else {

          this.checkVals.splice(this. checkedVals.indexOf(val), 1)

        }

      }

    }

  }

从上面的代码可以看出,复选框的实现要比前面介绍的文本框、单选按钮的实现要复杂得多。但当我们把复选框组件的方法进行分解,会发现其实也没有那么难。当该复选框的值包含在模型数组中时,shouldBeChecked 为 true,否则为 false。当它被勾选时,updateVals 将该复选框的值添加到数组。当它被取消勾选时,updateVals 将复选框的值从数组中删除。

v-model 如何在组件上工作?

由于 Vue 并不知道自定义组件的功能是什么,如果自定义组件用作和表单 Input 元素类似的功能,Vue 将该自定义组件视为与 v-model 实现原理相同。自定义组件实际的工作原理与文本输入框完全相同,只是在事件处理程序中,它不会将事件对象传递给它,而是期望将值直接传递给它。所以

1

<my-custom-component v-model="myProperty" />


代码等同于如下

1

<my-custom-component :value="myProperty" @input="val => myProperty = val" />


自定义组件可以使用 model 属性将上面的实现转换为如下代码实现:

1

2

3

4

5

6

7

8

export default {

  name: 'my-custom-component',

  model: {

    prop: 'foo',

    event: 'bar'

  },

  // ...

}

v-model 指令会查找 model 中所有的值,使用你在 prop 中指定的属性,代替之前使用 value 属性。同时它也将使用 event 中指定的事件,而不是 input 事件。所以上面的自定义组件示例将实际扩展为以下内容:

1

<my-custom-component :foo="myProperty" @bar="val => myProperty = val" />


这看起来不错,但如果我们要制作一个自定义单选按钮或复选框,可能就会有问题了。通过做一些修改,我们可以将 v-model 实现的逻辑转移到我们的自定义的单选按钮和复选框组件中。

使用 v-model 实现自定义单选按钮

与复选框相比,自定义单选按钮非常简单。下面是一个非常基本的自定义单选按钮,我设计的只是将输入文本框包装在 label 标签中,并接受 label 属性来添加标签文本。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

<template>

  <label>

    <input type="radio" :checked="shouldBeChecked" :value="value" @change="updateInput">

    {{ label }}

  label>

template>

: 'modelValue',

    event: 'change'

  },

  props: {

    value: {

      type: String,

    },

    modelValue: {

      default: ""

    },

    label: {

      type: String,

      required: true

    },

  },

  computed: {

    shouldBeChecked() {

      return this.modelValue == this.value

    }

  }

  methods: {

    updateInput() {

      this.$emit('change', this.value)

    }

  }

}


注意:我只写了用于解释 v-model 实现原理的 props 值,至于需要使用 input 的其他的几个属性(例如 name 或者 disabled),需要在 props 中创建,并传递给 input。你还需要通过添加 WAI-ARIA 属性来考虑可访问性,以及使用 slots(插槽) 来分发内容,而不是像我在 props 中所设置的 label 值那么简单。

你可能会认为,我在这个例子中没有包括 name 属性,一组单选按钮实际上将不会相互同步。实际上,模型的更新反过来会更新共享该模型的其他单选按钮,因此只要共享相同的模型,他们就不需要像普通的 HTML 表单那样共享一个 name 属性。

使用 v-model 实现自定义复选框

自定义复选框比单选按钮显然更复杂一些,主要是因为我们必须支持两种不同的用法:单个 true / false 复选框(可能使用或不使用 true-value 或 false-value)和多个将所有选中的 value 值添加到模型数组的复选框。

你可能会认为我们首先需要确定复选框元素是否具有相同的 name 属性,但这并不是 Vue 内部的实现方式。就像单选按钮一样,Vue 根本不考虑 name 属性。name 属性只是在提交原生表单时会用到。你可能会认为它是根据复选框是否共享相同的模型,但也不是这样的,它仅仅是通过模型是否是数组来判断是单个还是多个复选框。

因此,这段代码将依据自定义单选按钮的实现方式进行重构,在 sholdBeChecked 和 updateInput 函数中的需要根据 modelValue 是否是数组进行逻辑拆分。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

<template>

  <label>

    <input type="checkbox" :checked="shouldBeChecked" :value="value" @change="updateInput"







请到「今天看啥」查看全文


推荐文章
庞门正道  ·  准备好上班了吗?
2 天前
ZaomeDesign  ·  每日灵感丨二月五日
昨天
庞门正道  ·  21张剪纸,剪出来的全是震撼~
5 天前
字体设计  ·  招福万来,夜间飞行
1 周前
国际家居  ·  比利时极简设计,朴实无华
4 天前
国际家居  ·  比利时极简设计,朴实无华
4 天前
点点星光  ·  春节习俗完整版,终于找全了!
8 年前
来自星星  ·  2月8日十二星座运势分析
8 年前