vue自定义组件总结

组件系统是Vue.js其中一个重要的概念,它提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树。

一、四个核心组成

1、props 2、自定义事件 event 3、插槽及作用域插槽 slot 4、组件方法 method。
任何组件均离不开以上4点,我们在开发过程中,以这4点入手,封装我们想要的组件。

以element-ui table组件为例

Table Attributes 指的是 props
props data 显示的数据

Table Events 指的是 自定义事件
自定义事件 selection-change 当选择项发生变化时会触发该事件

Table Slot 插槽
append 插入至表格最后一行之后的内容,如果需要对表格的内容进行无限滚动操作,可能需要用到这个 slot。若表格有合计行,该 slot 会位于合计行之上。

Table-column Scoped Slot 插槽及作用域插槽 slot
Table-column 是 el-table-column组件,有一个默认插槽 自定义列的内容,参数为 { row, column, $index }

我们通常这样使用

<el-table-column
    fixed="right"
    label="操作"
    width="100">
      <template slot-scope="scope">
        <el-button @click="handleClick(scope.row)" type="text" size="small">查看</el-button>
        <el-button type="text" size="small">编辑</el-button>
      </template>
</el-table-column>

slot-scope="scope" 父组件通过scope访问子组件作用域。
Table Methods 指的是 组件方法
组件方法 clearSelection 用于多选表格,清空用户的选择 。 组件方法是通过 添加ref 索引,获取组件实例后调用。this.$refs.组件ref标识.组件方法

以上是组件核心概念。

二、组件注册

1、组件名
组件名应该始终是多个单词的,根组件 App 除外

这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的。

单文件组件的文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case)

混用文件命名方式有的时候会导致大小写不敏感的文件系统的问题,这也是横线连接命名同样完全可取的原因

使用 kebab-case
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>

Vue.component('my-component-name', { /* ... */ })

使用 PascalCase
当使用 PascalCase (驼峰式命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name> 和 <MyComponentName> 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的

Vue.component('MyComponentName', { /* ... */ })

2、全局注册
以上方法都属于全局注册, 也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中, 比如

HTML

<div id="app">
  <component-a></component-a>
  <component-b></component-b>
  <component-c></component-c>
</div>

JS

Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })
 
 
new Vue({ el: '#app' })

在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用.

3、局部注册

如果不需要全局注册,或者是让组件使用在其它组件内,可以用选项对象的 components 属性实现局部注册, 这里不做详述。

三、父子组件通信

在vue组件通信中其中最常见通信方式就是父子组件之中的通信,而父子组件的设定方式在不同情况下又各有不同。最常见的就是父组件为控制组件子组件为视图组件。父组件传递数据给子组件使用,遇到业务逻辑操作时子组件触发父组件的自定义事件。无论哪种组织方式父子组件的通信方式都是大同小异

父组件到子组件通讯

父组件到子组件的通讯主要为:子组件接受使用父组件的数据,这里的数据包括属性和方法(String, Number, Boolean, Object, Array, Function)。vue提倡单项数据流,因此在通常情况下都是父组件传递数据给子组件使用,子组件触发父组件的事件,并传递给父组件所需要的参数

通过 props 传递数据 (推荐)

父子通讯中最常见的数据传递方式就是通过props传递数据,就好像方法的传参一样,父组件调用子组件并传入数据,子组件接受到父组件传递的数据进行验证使用

props 可以是数组或对象,用于接收来自父组件的数据。props 可以是简单的数组,或者使用对象作为替代,对象允许配置高级选项,如类型检测、自定义校验和设置默认值

prop 的定义应该尽量详细,至少需要指定其类型

<!-- 父组件 -->
<template>
    <div>
        <my-child :parentMessage="parentMessage"></my-child>
    </div>
</template>
 
<script>
    import MyChild from '@components/common/MyChild'
 
    export default {
        components: {
            MyChild
        },
        data() {
            return {
                parentMessage: "我是来自父组件的消息"
            }
        }
    }
</script>
 
<!-- 子组件 -->
<template>
    <div>
        <span>{{ parentMessage }}</span>
    </div>
</template>
 
<script>
    export default {
        props: {
            parentMessage: {
                type: String,
                default: '默认显示的信息'
                // require: true // 必填
            }
        }
    }
</script>
子组件到父组件通讯

子组件到父组件的通讯主要为父组件如何接受子组件之中的数据。这里的数据包括属性和方法(String, Number, Boolean, Object, Array, Function)

通过 $emit 传递父组件数据 (推荐)

与父组件到子组件通讯中的$on配套使用,可以向父组件中触发的方法传递参数供父组件使用

<!-- 父组件 -->
<template>
    <div>
        <my-child @childEvent="parentMethod"></my-child>
    </div>
</template>
 
<script>
    import MyChild from '@components/common/MyChild'
 
    export default {
        components: {
            MyChild
        },
        data() {
            return {
                parentMessage: '我是来自父组件的消息'
            }
        },
        methods: {
            parentMethod({ name, age }) {
                console.log(this.parentMessage, name, age)
            }
        }
    }
</script>
 
 
<!-- 子组件 -->
<template>
    <div>
        <h3>子组件</h3>
    </div>
</template>
 
<script>
    export default {
        mounted() {
            this.$emit('childEvent', { name: 'zhangsan', age:  10 })
        }
    }
</script>
不推荐的通信方式

this.parent this.children
this.$refs

四、兄弟组件通信

1、vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,它是响应式的,状态发生变化,组件会更新。

vuex实现兄弟组件通信非常简单,组件A引用vuex数据,组件B通过方法改变vuex数据,vuex状态是响应式的,数据放生变化,组件A会更新。

2、eventBus又称为事件总线
在vue中可以使用eventBus来作为沟通桥梁, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所有组件都可以通知其他组件。

初始化

首先需要创建一个事件总线并将其导出, 以便其他模块可以使用或者监听它。

我们在src/components/目录下新建文件bus.js。

import Vue from 'vue'
export default new Vue()

发送事件

假设你有两个兄弟组件: ComA和ComB,ComA发送消息给ComB。

ComA这样

<template>
    <div>
        <button @click="sendMsg">给组件B发送消息</button> 
    </div>
</template>
 
<script>
import bus from './bus'
 
export default {
    name: 'comA',
    data () {
        return {
             
        }
    },
     
    methods: {
        sendMsg() {
            bus.$emit('fromA', {
                phone: 13800138000
            })
        }
    }
}
</script>

很显然,ComA中使用bus.$emit(事件名,数据);向事件中心注册发送事件。

接收事件

ComB接受ComA发送过来的消息。

<template>
    <div>
        <p>{{fromA}}</p>
    </div>
</template>
 
<script>
import bus from './bus'
 
export default {
    name: 'comB',
    data () {
        return {
            fromA: '',
        }
    },
    mounted() {
        bus.$on('fromA', param => {
            this.fromA = param.phone;
        })
    }
}
</script>

于是,当ComA发送了一个手机号码phone给ComB时,comB就会接收并显示。

父组件

在父组件中调用ComA和ComB两个兄弟组件。

<template>
  <div>
    <comA></comA>
    <comB></comB>
  </div>
</template>
 
<script>
import ComA from './ComA.vue'
import ComB from './ComB.vue'
 
export default {
    components: {
       ComA,
       ComB
    },
}
</script>

五、多层级组件通讯

1、vuex
2、eventBus
3、provide / inject

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

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。

// 父级组件提供 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}
 
// 子组件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}

六、作用域插槽

有时让插槽内容能够访问子组件中才有的数据是很有用的。例如,设想一个带有如下模板的 <current-user> 组件:

<span>
  <slot>{{ user.lastName }}</slot>
</span>

我们可能想换掉备用内容,用名而非姓来显示。如下:

<current-user>
  {{ user.firstName }}
</current-user>

然而上述代码不会正常工作,因为只有 <current-user> 组件可以访问到 user 而我们提供的内容是在父级渲染的。

为了让 user 在父级的插槽内容中可用,我们可以将 user 作为 <slot> 元素的一个 attribute 绑定上去:

<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>

绑定在 <slot> 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

在这个例子中,我们选择将包含所有插槽 prop 的对象命名为 slotProps,但你也可以使用任意你喜欢的名字。

七、组件的方法

组件方法,就是我们写组件时,在methods选项里边定义的一些方法,他通常是对数据的CURD。
element-ui我们常用的有

this.refs[formName].resetFields(); form表单重置 this.refs.[treeName].getCheckedKeys() tree 返回目前被选中的节点的 key 所组成的数组

八、vue 语法糖

1、v-model
v-model可以实现数据双向的绑定,自动为组件添加了props 名为 value 和 自定义事件 名为 input。

<input type="text" v-model="name">

实际上,上面的代码是下面代码的语法糖。

<input  v-bind:value="name"  v-on:input="name=$event.target.value"/>

自定义组件的 v-model

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:

Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})

现在在这个组件上使用 v-model 的时候:

<base-checkbox v-model="lovingVue"></base-checkbox>

这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的 property 将会被更新。
2、.sync 修饰符
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件都没有明显的变更来源。

这也是为什么我们推荐以 update:myPropName 的模式触发事件取而代之。举个例子,在一个包含 title prop 的假设的组件中,我们可以用以下方法表达对其赋新值的意图:

this.$emit('update:title', newTitle)

然后父组件可以监听那个事件并根据需要更新一个本地的数据 property。例如:

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

为了方便起见,我们为这种模式提供一个缩写,即 .sync 修饰符:

<text-document v-bind:title.sync="doc.title"></text-document>

九、组件选项推荐顺序

<script>
  export default {
    el: '#app', // 只在由 new 创建的实例中遵守。
     
    // 全局感知
    name: 'name', // 组件name
    parent: VueInstance, // 指定父实例
 
    // 组件类型
    functional: false, // 没有data 实例 没有上下文
 
    // 模板修改器
    delimiters: ['${', '}'], // 分隔符变成了 ES6 模板字符串的风格
    comments: false, // 当设为 true 时,将会保留且渲染模板中的 HTML 注释。默认行为是舍弃它们。
 
    // 模板依赖
    components: {}, // 子组件
    directives: {}, // 自定义指令
    filters: {}, // 自定义过滤器
 
    // 组合
    extends: CompA, // 扩展另一个组件 和 mixins 类似
    mixins: [tableEvents], // 混入选项对象, 混入实例对象可以像正常的实例对象一样
 
    // 接口
    inheritAttrs: true,
    model: { // 自定义组件在使用 v-model 时定制 prop 和 event
      prop: 'checked',
      event: 'change'
    },
    propsData: { // 只用于 new 创建的实例中。   创建实例时传递 props。
 
    },
 
    // 本地状态
    data: () => ({ // 本地状态
 
    }),
    computed: { // 计算属性
 
    },
 
    // 事件 生命周期钩子
    watch: {
 
    },
 
    // 生命周期钩子
    beforeCreate() {
 
    },
    created() {
 
    },
    beforeMount() {
 
    },
    mounted() {
 
    },
    beforeUpdate() {
 
    },
    updated() {
 
    },
    activated() {
 
    },
    deactivated() {
 
    },
    beforeDestroy() {
 
    },
    destroyed() {
 
    },
 
    // 非响应式的属性 (不依赖响应系统的实例属性)
    methods() {
 
    },
 
    // 渲染 (组件输出的声明式描述)
    template: '<div>demo</div>', // 渲染模板
    render: function (createElement) {
      return createElement(
        'h' + this.level,   // 标签名称
        this.$slots.default // 子节点数组
      )
    },
    renderError (h, err) { // 只在开发者环境下工作。
      return h('pre', { style: { color: 'red' }}, err.stack)
    }
     
  }
</script>

十、组件样式推荐使用CSS 作用域

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

推荐阅读更多精彩内容