如何实现Vue组件化

组件化是一门艺术,如何用好组件化优雅的实现页面,是每一个前端开发的必修课,最近学了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$rootvue

//兄弟组件1
this.$parent.$on('foo', handle)

//兄弟组件2
this.$parent.$emit('foo')
复制代码

祖先和后代之间

因为嵌套层数过多,传递props不切实际,vue提供了provide / inject API完成该任务。vuex

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 的缺点:bash

  • provide 和 inject 绑定并非可响应的。然而,若是你传入了一个可监听的对象,那么其对象的属性仍是可响应的。
  • provide 和 inject只能实现从祖辈组件向孙组件传值。

provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。app

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类负责事件派发、监听和回调管理async

// 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,经过它管理数据并通知组件状态变动ide

插槽

插槽语法是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;
}
复制代码
相关文章
相关标签/搜索