vue组件最佳实践

写在前面

  • 知识就是力量。 ——安琪拉
  • 知识就是力量,但更重要的是运用知识的技能。 —— 培根
  • 本文主要介绍vue组件间常见的通信方式,边界情况及插槽的使用。

何为组件化?

vue组件系统提供了一种抽象,让我们可以使用独立可复用的组件来构建大型应用,任意类型的应用界 面都可以抽象为一个组件树。组件化能提高开发效率,方便重复使用,简化调试步骤,提升项目可维护 性,便于多人协同开发。

通常一个应用会以一棵嵌套的组件树的形式来组织:例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。如下图所示:


组件树

1.组件常用通信方式

  1. 父传子:props
  2. 子传父:自定义事件 event
  3. 跨层级通信:事件总线 Bus
  4. 跨层级通信:专为 Vue.js 应用程序开发的状态管理模式 vuex

1.1 props

// 父组件
<Child1 msg="some message from parent"></Child1>

// Child1组件
<template>
    <div class="border">
        我是子组件1
        <div>{{msg}}</div>
    </div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String,
      default: ''
    }
  },
  data () {
    return {}
  }
}
</script>

结果:
props 父传子

1.2 event

// 父组件
<template>
    <div>
        <h2>组件通信</h2>
        <!-- props 父传子 -->
        <Child1 msg="some message from parent"></Child1>
        <!-- 自定义事件 event 子传父 -->
        <Child2 @clickSon="getMsg"></Child2>
        <p>Child2传过来的值是: {{child2Info}}</p>
    </div>
</template>

<script>
import Child1 from './Child1'
import Child2 from './Child2'

export default {
  components: {Child1, Child2},
  data () {
    return {
      child2Info: ''
    }
  },
  methods: {
    getMsg (info) {
      this.child2Info = info
    }
  }
}
</script>


// Child2组件
<template>
    <div @click="$emit('clickSon', 'msg from child2')" class="border">
        我是子组件2
    </div>
</template>

<script>
export default {
  data () {
    return {}
  },
  mounted () {
    this.$bus.$on('send', (msg) => {
      console.log('Child1 点击了, 信息是:' + msg)
    })
  }
}
</script>

结果:


自定义事件

1.3 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))
    }
  }
}

export default Bus

使用方式:

1.先在main.js中注册$bus

import Vue from 'vue'
import App from './App'
import router from './router'
import Bus from './bus'
Vue.prototype.$bus = new Bus()
// 实际上Vue已经实现了事件总线,我们使用的时候不用自定义Bus,直接使用如下方式引入即可
// Vue.prototype.$bus = new Vue()

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

2.在需要接受值的地方注册监听事件,这里已 Child1 与 Child2 之间的传值为例:

Child2

<template>
    <div @click="$emit('clickSon', 'msg from child2')" class="border">
        我是子组件2
    </div>
</template>

<script>
export default {
  data () {
    return {}
  },
  mounted () {
    // 组件挂载时即对send事件进行监听,监听到后打印信息
    this.$bus.$on('send', (msg) => {
      console.log('Child1 点击了, 信息是:' + msg)
    })
  }
}
</script>

Child1

<template>
    <div class="border">
        我是子组件1
        <button @click="$bus.$emit('send', '子组件1通过bus传来的值')">触发事件总线</button>
    </div>
</template>

结果:
点击Child1的button后,Child2打印:'Child1 点击了, 信息是: 子组件1通过bus传来的值'

1.4 vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。这里就不做举例啦~下期做个完成剖析给大家。
想学习的移步官网:vuex中文官方网站

2.边界情况

大多数情况下,并不推荐使用边界情况,而是使用更优的替代方案去代替边界情况。但是了解边界情况可以在理解源码封装组件化仓库中得到很大的便利。正如官网所说:在绝大多数情况下,我们最好不要触达另一个组件实例内部或手动操作 DOM 元素。不过也确实在一些情况下做这些事情是合适的。

2.1 $root——访问根实例

在每个 new Vue 实例的子组件中,其根实例可以通过 $root property 进行访问。

// Vue 根实例
new Vue({
  data: {
    foo: 1
  },
  computed: {
    bar: function () { /* ... */ }
  },
  methods: {
    baz: function () { /* ... */ }
  }
})

// 所有的子组件都可以将这个实例作为一个全局 store 来访问或使用:
// 获取根组件的数据
this.$root.foo
// 写入根组件的数据
this.$root.foo = 2
// 访问根组件的计算属性
this.$root.bar
// 调用根组件的方法
this.$root.baz()

2.2 $parent——访问父级组件实例

兄弟组件之间通信可通过共同祖辈搭桥

// brother1
this.$parent.$on('foo', handle) 
// brother2
this.$parent.$emit('foo')

2.3 $children——访问所有子组件实例或子元素

父组件可以通过children访问子组件实现父子通信。注意:children不能保证子元素顺序。

  // parent 
  this.$children[0].xx = 'xxx'

2.4 $refs——访问子组件实例或子元素

获取子节点引用

// parent
<HelloWorld ref="hw"/>

mounted() { 
    this.$refs.hw.xx = 'xxx'
}

2.5 provide/inject——依赖注入

能够实现祖先和后代之间传值

// 祖先元素
provide() {
    return {foo: 'foo'}
}

// 后代
<template>
    <div class="border">
        <p>{{foo}}</p>
    </div>
</template>

<script>
export default {
  inject: ['foo']
}
</script>
// 也可以在后代元素中设置别名,以防命名冲突
 inject: {
    bar: {
      from: 'foo'
    }
 }
访问的时候: <p>{{bar}}</p>

3.非prop特性

用于隔代透传,只能传一代。

3.1 $attrs

包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 ( class 和 style 除外)。当一个组件没有 声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外),并且可以通过 v- bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

// 祖先组件
 <Child2 msg="some message from parent"></Child2>
 
// Child2组件
<template>
    <div>
      <Grandson v-bind="$attrs"/>
    </div>
</template>

<script>
import Grandson from './Grandson'
export default {
  components: {Grandson},
  data () {
    return {}
  }
}
</script>

// 孙子组件
<template>
    <div class="border">
        <div>孙子组件</div>
        <div>父辈通过$attr透传过来的信息:{{msg}}</div>
    </div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String
    }
  }
}
</script>

结果:
在这里插入图片描述

3.2 $listeners

透传事件

// 祖先组件
 <Child2 @clickSon="getMsg"></Child2>
  methods: {
   getMsg (info) {
     console.log(info)
   }
 }
 
// Child2组件
<template>
    <div>
      <Grandson v-on="$listeners"/>
    </div>
</template>

<script>
import Grandson from './Grandson'
export default {
  components: {Grandson},
  data () {
    return {}
  }
}
</script>

// 孙子组件
<template>
    <div class="border" @click="$emit('clickSon', 'msg from grandSon')">
        <div>孙子组件</div>
    </div>
</template>

4.插槽

插槽语法是Vue实现的内容分发 API,用于复合组件开发。在通用组件库开发中有大量应用。

4.1 匿名插槽

// Layout
<div class="body">
  <slot></slot>
</div>

// parent
<Layout>
    <!-- 匿名插槽 -->
    <template>种一棵树最好是时间是十年前,而后是现在!</template>
</Layout>

4.2 具名插槽

// Layout
<div class="header">
  <slot name="header"></slot>
</div>

// parent
<Layout>
    <template v-slot:header>欢迎来到捡代码的小女孩的vue世界</template>
</Layout>

4.3 作用域插槽

// Layout
<div class="footer">
  <slot name="footer" :childValue="footerContent"></slot>
</div>

// parent
<Layout>
    <template v-slot:footer="value">{{value.childValue}}</template>
    <!-- 对象解构 -->
    <!-- <template v-slot:footer="{childValue}">{{childValue}}</template> -->
</Layout>

完整代码如下:

父组件

<template>
  <div>
    <h2>插槽</h2>
    <!-- 插槽 -->
    <Layout>
      <!-- 具名插槽 -->
      <template v-slot:header>欢迎来到捡代码的小女孩的vue世界</template>
      <!-- 匿名插槽 -->
      <template>种一棵树最好是时间是十年前,而后是现在!</template>
      <!-- 作用域插槽 -->
      <template v-slot:footer="value">{{value.childValue}}</template>
       <!-- 对象解构 -->
       <!-- <template v-slot:footer="{childValue}">{{childValue}}</template> -->
    </Layout>
  </div>
</template>

<script>
import Layout from './Layout.vue'
export default {
  name: 'Slot',
  components: {
    Layout
  }
}
</script>

Layout组件

<template>
  <div>
    <div class="header">
      <slot name="header"></slot>
    </div>
    <div class="body">
      <slot></slot>
    </div>
    <div class="footer">
      <slot name="footer" :childValue="footerContent"></slot>
    </div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      footerContent: '学习的敌人是自己的满足,要认真学习一点东西,必须从不自满开始'
    }
  }
}
</script>

<style scoped>
.header {
  background-color: rgb(252, 175, 175);
}
.body {
  display: flex;
  background-color: rgb(144, 250, 134);
  min-height: 100px;
  align-items: center;
  justify-content: center;
}
.footer {
  background-color: rgb(114, 116, 255);
}
</style>

效果如下:


插槽效果

这些就是组件化开发的必备知识啦 (〃'▽'〃)
get之后和我一起手写小组件吖~


The end~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容