v-model 指令在表单控件元素上创建双向数据绑定。根据控件类型它自动选取正确的方法更新元素。
比如,多个勾选框,绑定到同一个数组:
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames | json }}</span>
new Vue({
el: '...',
data: {
checkedNames: []
}
})
Vue 可以通过全局注册来实现全局组件的功能,比如有这么一个组件 exampleComponent ,如果想把它注册成全局组件的话,只需要在引入 Vue 的文件里调用 Vue.component('example-component',exampleComponent) 来实现,又或者如同大部分 Vue 的 ui框架 那样,直接调用 Vue.use(/* 组件 */) 来实现。
Vue.use() 方法比 Vue.component() 要复杂些,其大致用法如下:
这两种全局注册组件的方法在注册成功后都是通过使用标签名如 <example-component></example-component>来使用的。使用过 element 、 iview 等UI框架的朋友肯定会注意到这么一种比较特殊的组件,如 loading 和 message ,这类组件的使用场景大部分是在js执行环境时要用到,比如请求发送前要出现一个 loading 遮罩层防止用户重复请求,请求成功后这个遮罩层又要消失掉。这种需求若是也用预先在 html 中放置对应组件标签的形式的话,未免显得太过麻烦。所以,为了解决这个问题,有这么一种通过调用 Vue 原型方法来调用组件的方式。如 element 中 this.$message('这是一条消息提示')这样调用后在页面上显示一条消息提示的js方法。
不管什么组件,其本质都是操作DOM,只不过因为直接原生操作 DOM 会对浏览器的开销比较大, Vue 里面使用了一种虚拟dom的技术来尽可能的减少这种开销,而且操作dom虽然是一种很直观的改变显示效果的形式,但操作太过于繁琐。种种原因, Vue 的基本思想就是 数据驱动DOM ,尽量不要去亲手修改 DOM 。但凡事无绝对,上面所说的就是一种不操作 DOM 就难以绕开的一种便捷功能的实现。
那如何实现全局js方法调用组件的功能呢?
要实现这个目的,必须先了解两个东西: vm.$mount 、 Vue.extend() 。
翻看官方文档,找出了这两者的用法如下:
上面两个东西,简单理解就是用 Vue 自身的方法定义一个 html 标签,然后又用 Vue 的方法找到某个特定id的标签,将其内容替换掉。
那么用这两个特性,我们来创建一个能够自定义入参内容的 fullName 组件,它的功能是调用时,页面出现一个半透明遮罩层,页面中间显示入参内容。
步骤如下:
弹出型的组件,无论是 Dialog、Notification、Toast 还是其它的,都有一些共通的问题需要解决:
1. 多个弹层同时出现时,z-index 的顺序
2. 多个弹层同时出现时,共享遮罩层
3. 是否可以自我管理,典型的,定时或者点击遮罩层关闭弹层。
4. 有些弹层需要保留状态,比如你编辑某篇文章,不小心点叉了,再打开还在;而有些需要每次打开都是新的,比如后台中创建某个条目的弹出式表单,你创建过一个条目,下一次再开这个弹层,不应该保留上一个条目的信息
5. 组件放在哪,是否单例,这个和第4点放一起说
6. 如何支持快捷方式,比如喜闻乐见的挂在一个全局方法上,用时创建一个,关闭后销毁
7. 模态,阻止滚动
这些都是之前自己写组件库时遇到的问题,分享一点自己的实践~
1. z-index 的管理
通过设置 position 为 fixed 或 absolute 的方式制作弹层,多个弹层同时打开时,默认是以 dom 树中的位置来确定遮照次序的。当然,我们可以手动设置 z-index,但这麻烦不说,还容易出错。
我的做法是设置一个弹层基类组件,比如叫 Popup,更上层的 Dialog、Toast 之类都是基于它开发的。这个 Popup 组件保持一个单例计数器,比如这样
<script lang="coffe">
$count = 0
module.exports =
props:
# 控制打开还是关闭
open:
type: Boolean
default: false
mounted: ->
# 装载时递增
if @open
$count++
@zIndex = $count
watch:
open: (open) ->
# open 变化时,如果是打开就递增,否则递减
if open
$count++
@zIndex = $count
else
$count--
</script>
这样一来所有基于 Popup 的组件的 z-index 都是自动计算的,后打开的总是在前面。
2. 共享遮罩层
如果所有对话框实例都有自己的半透明遮照,同时打开后层层叠加会很难看(不过,也许有的设计师就喜欢这种效果?)。为了解决这个问题,可以在 Popup 上设置一个单例遮照层。也就是说,若两个以上 Dialog 打开时,Popup 判断已经有一个遮罩层了,那么自动将后面 Dialog 的透明度设为 0。
这里需要注意的是,并不是所有基于 Popup 的弹层式组件都有相同的透明度(比如有的是0.4,有的是 0.8),这里应该设置一个单例变量 maxOpacity,记录并设置为最深的遮照。
3. 外部触发与自我触发的折中
拿最常见的需求来说,Dialog 需要能够点击遮罩层关闭。Vue 1.0 中通过 sync 修饰符很容易做到内外部共享状态,但 2.0 中已经取消了 sync,可能是为了更清晰的状态变化管理,但我觉得 sync 在一些特殊场合确实很有用。
那么现在,一个典型的做法是完全交由外部来控制 Dialog 的开关,比如父组件通过 prop 设置一个布尔值 open,Dialog 发觉遮照被点击时,可以向上抛出个 click-mask 事件,由父组件来决定是否可以关闭。
其实通过 v-model 也可以实现类似 sync 的功效,我不确定语义上是否一定准确,点击遮照关闭对话框也算是用户输入,触发一个 input 事件是合理的,但类似 Toast 2 秒后关闭,这可能说不通。但是共享状态后,内外部都可以控制,实在是很方便,这个可以权衡?
4. 弹层每次打开,是否应该重绘。
我的做法是暴露一个 prop 布尔值 auto-reset 让使用者决定。如果需要每次都重回,就将 open 绑在 v-if 上,否则绑在 v-show 上:
<template lang="jade">
.soil-popup(
v-if="reset? open: true",
v-show="open"
)
slot
</template>
5. 组件放在哪,是否单例
这个根据弹层的功能需求决定。比如后台运营系统里,点击条目列表弹出个条目编辑框,这种天然高内聚的需求,可以与条目列表放在一个条目管理者组件中。而像 AlertDialog、ConfirmDialog 之类则适合放在全局位置。
单例也是看需求,像模态化的 AlertDialog 就一个出口,你肯定得先关闭一个再打开另一个,只需单例。其他需要同时存在的弹层类型,就要考虑多个了。这也是我不喜欢用声明式的写法去调用弹层的原因,看第6点。
6. 挂一个快捷方法,用时创建一个弹层,用完销毁。
很多弹层需要几个同时存在是不确定的( Notification、Toast 等 ),而且对于通用型的弹层,会在很多疙瘩角落里调用…… 我觉得虽然快捷方法违反 Vue 状态化管理组件的特性,但特殊需求还是应该特殊对待。
所以我会设置一个全局 alert 方法( CoffeeScript ):
Constructor = require './AlertDialog.vue'
VueComponent = Vue.extend Constructor
alert = (message, onOK) ->
vm = new VueComponent().$mount()
vm.message = message
vm.$on 'ok', onOK if onOK
vm.$on 'ok', ->
vm.open = false
document.body.removeChild(vm.$el)
setTimeout (->vm.$destroy()), 0
document.body.appendChild(vm.$el)
vm.open = true
记得销毁实例前要设置 open = false,因为要通知那个全局 z-index 计数器去递减
7. 模态,阻止滚动