[vue]组件化实践

116 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 6 天,点击查看活动详情

组件含义

在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。

组件注册

Vue.component(name, options)可用于注册组件。

Vue.component('component-name', { /* ... */ })
  • 组件命名

    组件最好使用kebab-case (短横线分隔命名)的方式;

    规范中:html标记和属性名称不区分大小写,XHTML标记和属性名称区分大小写,并且必须小写;

    推荐遵循 W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符),避免和当前以及未来的 HTML 元素相冲突

数据传递

父子组件传递数据 -- props

```html
    <!--在父组件中,自定义attribute ,将数据传递给子组件-->
    <course-list :courses="courses"></course-list>
```

```js
    // 在子组件中,可以用一个 props 选项将其包含在该组件可接受的 prop 列表中;
    // props可以是一个数组,推荐使用对象形式,可以对prop类型验证等。
    props: {
        courses: {
            type: Array,
            default: []
        }
    },
```

自定义事件及其监听

当子组件需要和父级组件进行通信,可以派发并监听自定义事件。-- $emit


```html
    <!--父组件中监听事件-->
    <!--推荐html标签属性全小写-->
    <course-add @add-course="addCourse"></course-add>
```

```js
    data() {
        return: {
            course: ''
        }
    }
    methods: {
        // 在子组件中派发事件,通知父组件
        addCourse() {
            // 大小写应与html中的相匹配
            this.$emit('add-course', this.course)
        },
    }
    
```

自定义组件实现双绑

使用v-model,v-model实际就是个语法糖,等价于:value➕@input

将数据值交给父组件掌控:
```html
    <!--父组件使用子组件,使用v-model-->
    <!--v-model="course" 就等价于 :value = "course" ➕ @input="course=$event"-->
    <course-add v-model="course" @add-course="addCourse"></course-add>
```

```js
    // 自定义组件支持v-model需要实现内部input的:value和@input
    Vue.component('course-add', {
        props:['value'],
        template: `
        <div>
            <input type="text" :value="value" @input="handleInput" @keydown.enter="addCourse"> 
            <button @click="addCourse">添加课程</button>
        </div>
        `,
        methods: {
            addCourse() {
                this.$emit('add-course')
            },
            handleInput(e) {
                console.log(e)
                this.$emit('input', e.target.value)
            }
        }
    })
```

插槽

<slot> 元素作为承载分发内容的出口

自定义的组件,可以当做html标签一样使用,标签内包含的内容,可以通过<slot>元素,将标签包含的内容与自定义组件本身合成

比如上面使用的组件都是这样的:标签内部并没有其他内容

    <course-add @add-course="addCourse"></course-add>
    <course-list :courses="courses"></course-list>

现在如果我们在标签内添加内容如何展示呢?
如:实现一个提示信息弹框,提示信息由父组件决定(谁用谁决定)

    <!--父组件使用-->
    <!-- 插槽 提示框 -->
    <message :isshow="isShow" @close="isShow = $event"> 添加课程成功!
    </message>
    // message组件
    // 提示框
    Vue.component('message', {
        props: ['isshow'],
        template: `
        <div v-show='isshow' class="message-box">
            <slot/>
            <span class="message-box-close" @click="close"> X </span>
        </div>
        `,
        methods: {
            close() {
                this.$emit('close', false)
            },
        }
    })
    
    
    

如果上面message组件中没有使用slot元素,那么使用该组件时,其开始到结束标签内的所有内容都会被抛弃。

通过.sync修饰符可以简化

    <!--v-show.sync="isShow"相当于 :isshow="isShow" 加上 @update:isshow="isShow=$event"-->
    <message :isshow.sync="isShow"> 添加课程成功!</message>

直接$emit规定好的update:myPropName,也就是 update:isshow

    Vue.component('message', {
        props: ['isshow'],
        template: `
        <div v-show='isshow' class="message-box">
            <slot/>
            <span class="message-box-close" @click="$emit('update:isshow', false)"> X </span>
        </div>
        `,
    })
    

具名插槽

把指定的内容放到指定的内容

<message :isshow.sync="isShow"> 
    <template v-slot:header>
        巴啦啦小魔仙全身变
    </template>
    添加课程成功!
    <template v-slot:footer>
        不积跬步无以至千里
    </template>
    <div>测试其他的</div>
</message>
Vue.component('message', {
    props: ['isshow'],
    template: `
    <div v-show='isshow' class="message-box">
        <slot name='header'/>
        <slot/>
        <span class="message-box-close" @click="$emit('update:isshow', false)"> X </span>
        <slot name='footer'/>
    </div>
    `,
})

指定名字的内容会在对应名字的地方显示,不带名字的默认显示在slot中,一个不带 name 的 <slot> 出口会带有隐含的名字“default”。

作用域插槽

让插槽内容能够访问子组件中才有的数据

  • 使用方式
  1. <slot> 元素上绑定 attribute,这个绑定在 <slot> 元素上的 attribute 被称为插槽 prop
// 组件
Vue.component('message', {
    props: ['isshow'],
    data() {
        return {
            title: '提示'
        }
    },
    template: `
    <div v-show='isshow' class="message-box">
        <slot :title='title'/>
    </div>
    `,
})

  1. 在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字
// 父级
<message :isshow.sync="isShow"> 
    <template v-slot:default="data">
        {{ data.title }}
    </template>
</message>
  1. 当且仅当被提供的内容只有默认插槽时,组件的标签可以被当作插槽的模板来使用,且可以直接简写成不带参数的 v-slot
<message :isshow.sync="isShow" v-slot:default="data"> 
    {{ data.title }}
</message>
<message :isshow.sync="isShow" v-slot="data"> 
    {{ data.title }}
</message>
  1. 默认插槽的缩写语法不能和具名插槽混用,它会导致作用域不明确
<message :isshow.sync="isShow" v-slot:default="data"> 
    {{ data.title }}
    <template v-slot:other="other">
        slotProps is NOT available here
    </template>
</message>

image.png

  1. 只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法
<message :isshow.sync="isShow" v-slot:default="data"> 
            
    <template v-slot:default="data">
        {{ data.title }}
    </template>
    <template v-slot:other="other">
        slotProps is NOT available here
    </template>
</message>

动态组件

通过 Vue 的 <component> 元素加一个特殊的 is attribute 来实现

vue组件化的理解

  1. 定义 组件是可复用的 Vue 实例,准确讲它们是VueComponent的实例,继承自Vue
  2. 优点 组件化可以增加代码的复用性、可维护性和可测试性。
  3. 使用场景
    • 通用组件:实现最基本的功能,具有通用性、复用性,例如按钮组件、输入框组件、布局组件等。
    • 业务组件:它们完成具体业务,具有一定的复用性,例如登录组件、轮播图组件。
    • 页面组件:组织应用各部分独立内容,需要时在不同页面组件间切换,例如列表页、详情页组件
  4. 如何使用
    • 定义:Vue.component()components选项,sfc
    • 分类:有状态组件,functional,abstract
    • 通信:props,emit()/emit()/on(),provide/inject,children/children/parent/root/root/attrs/$listeners
    • 内容分发:<slot><template>,v-slot
    • 使用及优化:is,keep-alive,异步组件

vue中的组件经历如下过程 组件配置 => VueComponent实例 => render() => Virtual DOM=> DOM 所以组件的本质是产生虚拟DOM