组件化是一门艺术,如何用好组件化优雅的实现页面,是每个前端开发的必修课,最近学了vue组件化的相关课程,本文就对常用的vue组件化技术的进行总结整理。
父子组件通信
父组件向子组件通信
属性prop
//父组件
<HelloWorld msg="Welcome to Your Vue.js App"/>
//子组件
props: { msg: String }
refs引用
//父组件
<HelloWorld ref="hw"/>
this.$refs.hw.xx
- 当父组件为自定义组件时,refs获得的是自定义组件实例
- 当父组件为HTML元素时,refs获得的时DOM元素
子组件向父组件通信
//子组件
this.$emit('add', good)
//父组件
<Cart @add="cartAdd($event)"></Cart>
兄弟组件:通过共同祖辈组件通信
通过共同的祖辈组件搭桥,$parent或$root。
//兄弟组件1
this.$parent.$on('foo', handle)
//兄弟组件2
this.$parent.$emit('foo')
祖先和后代之间
由于嵌套层数过多,传递props不切实际,vue提供了provide / inject API完成该任务。
provide / inject
- provide:包含一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。
- injecct:一个字符串数组,或一个对象,对象的 key 是本地的绑定名,value 是:在可用的注入内容中搜索用的 key (字符串或 Symbol),或一个对象,该对象的:from 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol)default 属性是降级情况下使用的 value。
//祖辈组件
<template>
<div id="app">
<router-view v-if="isRouterAlive" />
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
isRouterAlive: true
},
// 父组件中返回要传给下级的数据
provide () {
return {
reload: this.reload
}
},
methods: {
reload () {
this.isRouterAlive = false
this.$nextTick(() => {
this.isRouterAlive = true
})
}
}
}
</script>
//孙组件
<template>
<popup-assign
:id="id"
@success="successHandle"
>
<div class="confirm-d-tit"><span class="gray-small-btn">{{ name }}</span></div>
<strong>将被分配给</strong>
<a
slot="reference"
class="unite-btn"
>
指派
</a>
</popup-assign>
</template>
<script>
import PopupAssign from '../PopupAssign'
export default {
//引用vue reload方法
inject: ['reload'],
components: {
PopupAssign
},
methods: {
async successHandle () {
this.reload()
}
}
}
</script>
provide / inject 的优点:
- 祖先组件不需要知道哪些后代组件使用它提供的属性
- 后代组件不需要知道被注入的属性来自哪里
provide / inject 的缺点:
- provide 和 inject 绑定并不是可响应的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
- provide 和 inject只能实现从祖辈组件向孙组件传值。
provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。
dispatch:后代给祖先传值
// 定义一个dispatch方法,指定要派发事件名称和数据
function dispatch(eventName, data) {
let parent = this.$parent
// 只要还存在父元素就继续往上查找
while (parent) {
// 父元素用$emit触发
parent.$emit(eventName,data)
// 递归查找父元素
parent = parent.$parent
}
}
// 使用,HelloWorld.vue
<h1 @click="dispatch('hello', 'hello,world')">{{ msg }}</h1>
// App.vue
this.$on('hello', this.sayHello)
任意两个组件之间:事件总线 或 vuex
事件总线
创建一个Bus类负责事件派发、监听和回调管理
// Bus:事件派发、监听和回调管理
class Bus{
constructor(){
this.callbacks = {}
}
$on(name, fn){
this.callbacks[name] = this.callbacks[name] || []
this.callbacks[name].push(fn)
}
$emit(name, args){
if(this.callbacks[name]){
this.callbacks[name].forEach(cb => cb(args))
}
}
}
// main.js
Vue.prototype.$bus = new Bus() // child1
this.$bus.$on('foo', handle) // child2
this.$bus.$emit('foo')
vuex
创建唯一的全局数据管理者store,通过它管理数据并通知组件状态变更
插槽
插槽语法是Vue实现的内容分发API,用于复合组件开发,该技术在通用组件库开发中有大量应用。
Vue 2.6.0之后采用全新v-slot语法取代之前的slot、slot-scope
匿名插槽
// comp1
<div>
<slot></slot>
</div>
// parent
<comp>hello</comp>
具名插槽
// comp2
<div>
<slot></slot>
<slot name="content"></slot>
</div>
// parent
<Comp2>
<!-- 默认插槽用default做参数 -->
<template v-slot:default>具名插槽</template>
<!-- 具名插槽用插槽名做参数 -->
<template v-slot:content>内容...</template>
</Comp2>
作用域插槽
// comp3
<div>
<slot :foo="foo"></slot>
</div>
// parent
<Comp3>
<!-- 把v-slot的值指定为作用域上下文对象 -->
<template v-slot:default="ctx">
来自子组件数据:{{ctx.foo}}
</template>
</Comp3>
作用于插槽可以实现组件和业务的剥离,适合应用于至少包含三级以上的组件层级,是一种优秀的组件化方案。
案例分析
有一个购物网站,网站里有诸如 “猜你喜欢”,“每日特价”等商品列表,我们可以将这个页面进行组件化拆分。
- ColumnList:存放各类列表的组件。
- CommodityList:展示各类商品信息的列表组件。
- Commodity:商品信息组件。
当我们想要实现点击具体商品跳转到相应的详情页面,并希望该点击能在ColumnList组件中实现时,传统做法,会将在Commodity的点击事件$emit层层上抛至组件ColumnList,并在ColumnList中进行监听。但这种方法,使得子组件与业务紧耦合,利用作用域插槽就可以优雅的解决这个问题。
- 在ColumnList实现点击事件
- 利用template的
v-slot属性赋值上下文对象 - 在Commodity组件上绑定点击事件
组件化技巧
属性绑定
//父组件
<my-input type="text" autocomplete placeholder="Please input something"></my-input>
//子组件
<div>
<input v-bind="$attrs" />
</div>
export default {
inheritAttrs: false
}
//渲染结果
<div>
<input type="text" autocomplete placeholder="Please input something" />
</div>
inheritAttrs:默认情况下父作用域的不被认作 props 的特性绑定 (attribute bindings) 将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。通过设置 inheritAttrs 到 false,这些默认行为将会被去掉。$attrs:包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。
组件实例创建函数
针对例如弹窗等短暂出现的组件,可以通过一个实例创建函数进行调用。 优点:
- 当组件出现时挂载,消失时卸载
- 不需要进行注册
- 避免内存泄漏
import Vue from 'vue';
export default function create(component, props) {
const vm = new Vue({
render(h) {
return h(component, {props});
}
}).$mount();
//vm.$el为Vue实例使用的根DOM元素
document.body.appendChild(vm.$el);
//vm.$children为当前实例的直接子组件
const comp = vm.$children[0];
comp.remove = function() {
document.body.removeChild(vm.$el);
vm.$destroy();
}
return comp;
}