"组件" 是 vue 中比较基础的概念, 但我发现, 许多人对 Vue 组件的概念和由来并不是清楚. 因此, 我希望通过这个专题, 带大家换个角度来分析, 最终让大家更清楚组件开发.
image
首先, 我们先不谈组件, 我想问大家一个问题:
我们平常用任何编程语言写方法 (method) 的时候, 当一个方法里的逻辑过多时, 我们会怎么办? 当多个方法里有很多相似的逻辑时, 我们该怎么办?
答案很明了: 拆分成一个独立的方法.
如果拆分后还是有类似问题呢? 那就继续拆分.
类比一下, html 也一样. 当我们写了一大堆的 HTML 后, 发现有不少类似或重复的地方, 我们也可以按照拆分的方法, 拆分 HTML. 除了拆分 HTML 之外, 我们还可以拆分针对这段 HTML 书写的逻辑, 甚至是样式. 拆分后的 HTML, 逻辑, 样式组合在一起, 我们就称之为组件.
这么讲似乎有点抽象, 我们来举个例子吧. 假如我们要做一个简单的 TodoList 项目, 代码如下:
<ul> <li> <input type="checkbox"> <span> 学习 Vue 属性 </span> <button> 删除 </button> </li> <li> <input type="checkbox"> <span> 学习 Vue 事件 </span> <button> 删除 </button> </li> <li> <input type="checkbox"> <span> 学习 Vue 插槽 </span> <button> 删除 </button> </li> </ul></div>
可以看到, 我们 li 标签内的内容越来越多, 似乎可以把它独立出来, 在 Vue 中, 通过以下代码就可以将这部分代码独立出来.
通过 Vue.component 定义 (注册) 一个组件, 起名为 todo-item, 组件的 HTML 写在 template 字段上:
Vue.component('todo-item', { template: `<li> <input type="checkbox"> <span> 学习 Vue 属性 </span> <button> 删除 </button> </li>`})
然后, 你可以通过下面这样的方式来使用这个组件:
<ul> <todo-item></todo-item> <todo-item></todo-item> <todo-item></todo-item> </ul></div>
当然前提条件是你要先 new Vue 一个实例, 并添加挂载点
new Vue({ el: '#app' // 提供一个挂载点, 这样我们就可以在里面使用 todo-item 了})
这样一来清爽了许多, 可是这样就变成三个 "学习 Vue 属性" 事项了, 我们还缺 "学习 Vue 事件" 和 "学习 Vue 插槽" , 怎么办呢, 这就要用到 Vue 的属性了.
属性
我们接着改改, 调用方法时, 可以传递不同的参数, 方法也可以接收参数, 执行不同的逻辑, 加载组件时同样也可以传递不同的参数 (属性), 组件也可以接收参数 (属性) 来显示不同的内容:
Vue.component('todo-item', { props: ['item'], // 声明能接收的参 (属) 数 (性) // {{item}} 使用传递过来的 item template: `<li> <input type="checkbox"> <span>{{item}}</span> <button> 删除 </button> </li>`}) <div id="app"> <ul> <todo-item item="学习 Vue 属性"></todo-item> <todo-item item="学习 Vue 事件"></todo-item> <todo-item item="学习 Vue 插槽"></todo-item> </ul></div>
我们再精简一下:
<ul> <todo-item v-for="item in list" :item="item"></todo-item> </ul></div>new Vue({ el: '#app', data() { return { list: ['学习 Vue 属性', '学习 Vue 事件', '学习 Vue 插槽'] } }})
这样就可以了, 是不是很简单?
事件
我现在已经学完了 Vue 属性, 想要从 todolist 里面把它删除掉, 这好像并不太容易. 这时, 我需要给 button 绑定一个事件, 当然 Vue 提供给我们了一个简单的方式进行绑定事件, 用 @xxx 就可以进行事件绑定了 (这里的 xxx 指的任一字符串, 根据你的实际需要来命名就行)
Vue.component('todo-item', { props: ['item'], template: `<li> <input type="checkbox"> <span>{{item}}</span> <button @click="handleClick"> 删除 </button> </li>`, methods: { handleClick() { } }})
然后我们需要把点击事件告诉我们的上层 (父组件),Vue 同样给我们提供了一个 API:this.$emit('xxx', ...), 我们既然是做删除操作, 那就是叫 delete 好了, 我们还可以传递更多的参数, 如 this.item:
handleClick() { this.$emit('delete', this.item)}
上层组件还缺少了一个用来接收 delete 的地方, 我们可以通过 @delete 的方式来绑定一个用来接收 delete 事件的方法:
<todo-item v-for="item in list" :item="item" @delete="handleDelete">
最后只需要在 methods 字段上定义一个 handleDelete 方法, 改变 list 数组就完成了我们的删除操作:
new Vue({ el: '#app', data() { return { list: ['学习 Vue 属性', '学习 Vue 事件', '学习 Vue 插槽'] } }, methods: { handleDelete(item) { const index = this.list.findIndex(text=>text === item); this.list.splice(index, 1); } }})
以上就是 Vue 中事件的用法.
点击此处, 深入学习 Vue 中组件的事件
插槽
现在我想让这个 TodoList 中的 "学习 Vue XXX" 前加个图标 Icon, 怎么办呢? 还好 Vue 早就帮我想到了, 我们不能再通过属性传递这些带有标签的内容, 而是通过一种名叫 "插槽" 的东西进行传递:
<todo-item v-for="item in list" :item="item" @delete="handleDelete"> <span> 我是 Icon</span></todo-item>
当然我们也不能再用双括号来解析, 我们需要使用 这种写法来解析:
template: `<li> <input type="checkbox"> <span>{{item}}</span> <slot></slot> <button @click="handleClick"> 删除 </button> </li>`,
这种我们称之为默认插槽.
现在我想更进一步, 添加两个图标, 一个在文字前面, 一个在文字后面, 没问题:
<todo-item v-for="item in list" :item="item" @delete="handleDelete"> <span slot="prefixIcon"> 我是前缀 Icon</span> <span slot="suffixIcon"> 我是后缀 Icon</span></todo-item>
同样 template 需要更改:
template: `<li> <input type="checkbox"> <slot name="prefixIcon"></slot> <span>{{item}}</span> <slot name="suffixIcon"></slot> <button @click="handleClick"> 删除 </button> </li>`,
这便是我们的具名插槽.
如果想让功能更加丰富的话, 比如我想根据我的 input checkbox 的是否选中来改变图标的颜色, 该怎么做?
第一步, 记录我们 input 的选中状态, 我们使用 Vue 的 v-model 进行 input 的双向绑定:
Vue.component('todo-item', { props: ['item'], data() { return { checked: false, // 默认不选中 } }, template: `<li> <input type="checkbox" v-model="checked"> <slot name="prefixIcon"></slot> <span>{{item}}</span> <slot name="suffixIcon"></slot> <button @click="handleClick"> 删除 </button> </li>`, methods: { handleClick() { this.$emit('delete', this.item) } }})
状态有了, 现在需要把这个状态传递给上层:
<slot name="prefixIcon" v-bind="{checked}"> 我是前缀 Icon</slot>
接收到状态后并根据状态提供不同颜色的图标:
<span slot="prefixIcon" slot-scope="props" :style="{color: props.checked ?'red':'blue'}"> 我是前缀 Icon</span>
这就是我们的作用域插槽.
为了方便理解, 插槽使用方式我们使用了 Vue 2.5 版本的语法进行讲解
来源: http://www.jianshu.com/p/8571c7be688e