组件(一)

组件注册

组件(Component)是Vue.js的最核心功能,是可扩展的HTML元素(可看作自定义的HTML元素),是封装可重用的代码,同时也是Vue实例,可以接受与Vue相同的选项对象并提供相同的生命周期钩子。

为了能在UI模板中使用组件,必须先注册组件以便Vue识别。

有两种组件的注册类型:全局注册和局部注册。

全局注册

const app = Vue.createApp({

})

app.component('component-a', {   

         //选项

})

app.component('component-b', {     

        //选项

})

 app.component的第一个参数component-a组件的名称(自定义标签),组件名称推荐全部小写包含连字符(即有多个单词),避免与HTML元素相冲突。     注册后任何Vue实例都可以使用这些组件

示例代码如下:

<div id="app">     

        <component-a></component-a>   

        <component-b></component-b>     

        <component-c></component-c>

</div>

案例 

<template id="button-counter">
    <button @click="count++">You clicked me {{ count }} times.</button>
</template>
<div id="components-demo">
    <!-- 在模板中任意使用组件 -->
    <!-- 每个组件都各自独立维护它的count。因为每用一次组件。就会有一个它的新实例被创建 -->
    <button-counter></button-counter><br><br>
    <button-counter></button-counter><br><br>
    <button-counter></button-counter>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
    // 创建一个Vue 应用
    const app = Vue.createApp({})
    // 定义一个名为 button-counter 的全局组件(注册)
    app.component('button-counter', {
        data() {
            return {
                count: 0
            }
        },
        // 组件显示内容
        template: '#button-counter'
    })
    app.mount('#components-demo')
</script>

局部注册

全局注册往往是不够理想的。

比如,使用webpack构建系统,全局注册的组件,即使不再使用,仍然被包含在最终的构建结果中,造成用户无意义的下载JavaScript。

局部注册的组件只在该组件作用域下有效。

例如,希望ComponentA在ComponentB中可用,需要在ComponentB中,使用components选项局部注册ComponentA。

const ComponentA = {     

        /* ... */

}

const ComponentB = {

        components: {     'component-a': ComponentA

        }     //……

}

 使用props传递数据

在组件中,使用选项props来声明从父级组件接收的数据,props的值可以是两种,一种是字符串数组,一种是对象。

案例

构造两个数组props,一个数组接收来自父级组件的数据message(实现静态传递),一个数组接收来自父级组件的数据id和title(实现动态传递),并将它们在组件模板中渲染。

<template id="parent">
    <h4>{{ message }}</h4>
    <children v-for="post in posts" :id="post.id" :title="post.title"></children>
    <children v-for="post in posts" v-bind="post"></children>
</template>

<template id="children">
    <h4>{{id}} : {{ title }}</h4>
</template>
<div id="demo">
    <parent message="来自父组件的消息"></parent>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
    const messageApp = Vue.createApp({})
    messageApp.component('parent', {
        data() {
            return {
                //posts是对象数组
                posts: [
                    { id: 1, title: 'A' },
                    { id: 2, title: 'B' },
                    { id: 3, title: 'C' }
                ]
            }
        },
        props: ['message'],
        components: {
            'children':{
                props: ['id','title'],
                template: '#children'
            }
        },
        template: '#parent'
    })
    messageApp.mount('#demo')
</script>

单项数据流

使用props实现数据传递都是单向的,即父组件数据变化时,子组件中所有的prop将刷新为最新的值,但是反过来不行。这样设计的原因是尽可能将父子组件解耦,避免子组件无意中修改父组件的状态。如果业务中,需要改变prop时,一种是父组件传递初始值进来,子组件将它作为初始值保存起来,在子组件自己的作用域下随意修改;一种是使用计算属性修改。

案例

在子组件中声明数据count保存来自父组件的mycount,count的改变不影响mycount。

<template id="child-app">
    <button @click="count++">You clicked me {{ count }} times.</button>
</template>

<div id="app">
    父组件的计件数:{{mycount}}<br>
    <child-counter :parent-count="mycount"></child-counter>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
    const app = Vue.createApp({
        data() {
            return {
                mycount: 100
            }
        }
    })
    app.component('child-counter', {
        props: ['parentCount'],
        data() {
            return {
                count: this.parentCount
            }
        },
        template: '#child-app'
    })
    app.mount('#app')
</script>

数据验证

使用props实现数据传递的同时,还可以为props指定验证要求。一般当你的组件需要提供给别人使用时,最好进行数据验证。例如某个数据必须是数字类型,如果传入字符串,Vue将在浏览器控制台中弹出警告。

为了定制props的验证方式,可以为props的值提供带有验证需求的对象,而不是字符串数组。

案例

给组件的props提供带有验证需求的对象。

<template id="validate">
    <div>
        <h4>{{  num  }}</h4>
        <h4>{{  strnum  }}</h4>
        <h4>{{  isrequired  }}</h4>
        <h4>{{  numdefault  }}</h4>
        <h4>{{  objectdefault  }}</h4>
        <h4>{{  myfun  }}</h4>
    </div>
</template>
<div id="demo">
    <validate-post
        :num="200"
        :strnum="'sdf'"
        :isrequired="'abc'"
        :numdefault="300"
        :objectdefault="{a:'a'}"
        :myfun="'aaa'"
    ></validate-post>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
    const messageApp = Vue.createApp({})
    messageApp.component('validate-post', {
        props: {
            num: Number,
            strnum: [String, Number],
            isrequired: {
                type: String,
                required: true
            },
            numdefault: {
                type: Number,
                default: 100
            },
            objectdefault: {
                type: Object,
                default: function () {
                    return { message: 'hello' }
                }
            },
            myfun: {
                validator: function (value) {
                    alert(['success', 'warning', 'danger'].indexOf(value) !== -1)
                    return ['success', 'warning', 'danger'].indexOf(value) !== -1
                }
            }
        },
        template: '#validate'
    })
    messageApp.mount('#demo')
</script>

组件通信

props可以实现父组件向子组件传递数据,即通信。但Vue组件通信的场景有多种,包括父子组件通信、兄弟组件通信、组件链通信。

使用自定义事件通信

可通过props从父组件向子组件传递数据,并且这种传递是单向的。当需要从子组件向父组件传递数据时,需要首先给子组件自定义事件并使用$emit(事件名, 要传递的数据)方法触发事件,然后父组件使用v-on或@监听子组件的事件。

案例

子组件触发两个事件,分别实现字体变大变小。

<template id="blog">
    <h4>{{id}} : {{ title }}</h4>
    <button @click="$emit('enlarge-text', 0.1)">变大</button>
    <button @click="$emit('ensmall-text', 0.1)">变小</button>
</template>
<div id="demo">
    <div v-bind:style="{ fontSize: postFontSize + 'em' }">
        <blog-post v-for="post in posts" v-bind="post"
         @ensmall-text="postFontSize -= $event"
         @enlarge-text="onEnlargeText"></blog-post>
    </div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
    const blogApp = Vue.createApp({
        data() {
            return {
                //posts是对象数组
                posts: [
                    { id: 1, title: 'A' },
                    { id: 2, title: 'B' },
                    { id: 3, title: 'C' }
                ],
                postFontSize: 1
            }
        },
        methods: {
            onEnlargeText(enlargeAmount) {
                this.postFontSize += enlargeAmount
            }
        }
    })
    blogApp.component('blog-post', {
        props: ['id','title'],
        template: '#blog'
    })
    blogApp.mount('#demo')
</script>

使用v-model通信

除了自定义事件实现子组件向父组件传值外,还可以在子组件上使用v-model向父组件传值,实现双向绑定。

案例

使用v-model实现子组件向父组件传值,并实现双向绑定。

<template id="custom">
    <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">
</template>
<div id="demo">
    {{searchText}}<br><br>
    <custom-input v-model="text"></custom-input><br><br>
    <custom-input :model-value="text" @update:model-value="searchText = $event"></custom-input>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
    const blogApp = Vue.createApp({
        data() {
            return {
                text: '张三'
            }
        }
    })
    blogApp.component('custom-input', {
        props: ['modelValue'],
        template: '#custom'
    })
    blogApp.mount('#demo')
</script>

使用mitt实现非父子组件通信

在Vue.js中,推荐使用一个空的Vue实例作为媒介(中央事件总线)实现父子组件、兄弟组件及组件链通信。比如我们生活中的例子:买房卖房中介帮忙,买卖双方通过房产中介(中央事件总线)实现需求对接。

在Vue 2.x中,Vue实例可通过事件触发API($on、$off 和 $once)实现中央事件总线,但是在Vue 3.x中,移除了$on、$off 和 $once 实例方法,推荐使用外部库mitt来代替$on、$emit和$off实例方法。

案例

首先使用mitt新建一个中央事件总线bus,然后分别创建两个Vue实例buyer(买方)和seller(卖方),买卖双方互相通信。

 mitt.js

/**
* @param 入参为 EventHandlerMap 对象
 * @returns 返回一个对象,对象包含属性 all,方法 on,off,emit
 */
function mitt(all) {
    /*
      此处实参可传一个EventHandlerMap对象,实现多个 mitt 的合并。例如:
      const m1 = mitt();
      m1.on('hi', () => { console.log('Hi, I am belongs to m1.'); });
      const m2 = mitt(m1.all);
      m2.emit('hi') // Hi, I am belongs to m1.
      m2.on('hello', () => { console.log('Hello, I am belongs to m2.'); });
      m1.emit('hello'); // Hello, I am belongs to m2.
      m1.all === m2.all // true
    */
    all = all || new Map();
    return {
        // 事件键值对映射对象
        all,
        /**
         * 注册一个命名的事件处理
         * @param type 事件名,官方表示事件名如 *,用来标记为通用事件,调用任何事件,都会触发命名为 * 的事件
         * @param handler 事件处理函数
         */
        on(type, handler) {
            // 根据type去查找事件
            const handlers = all.get(type);
            // 如果找到有相同的事件,则继续添加,Array.prototype.push 返回值为添加后的新长度,
            const added = handlers && handlers.push(handler);
            // 如果已添加了type事件,则不再执行set操作
            if (!added) {
                all.set(type, [handler]); // 注意此处值是数组类型,可以添加多个相同的事件
            }
        },
        /**
         * 移除指定的事件处理
         * @param type 事件名,和第二个参数一起用来移除指定的事件,
         * @param handler 事件处理函数
         */
        off(type, handler) {
            // 根据type去查找事件
            const handlers = all.get(type);
            // 如果找到则进行删除操作
            if (handlers) {
                handlers.splice(handlers.indexOf(handler) >>> 0, 1);
            }
        },
        /**
         * 触发所有 type 事件,如果有type为 * 的事件,则最后执行。
         * @param type 事件名
         * @param evt 传递给处理函数的参数
         */
        emit(type, evt) {
            // 找到type的事件循环执行
            (all.get(type) || []).slice().map((handler) => { handler(evt); });
            // 然后找到所有为*的事件,循环执行
            (all.get('*') || []).slice().map((handler) => { handler(type, evt); });
        }
    };
}
<div id="buyer">
    <h1>显示卖方消息:{{ message1 }}</h1>
    <button @click="transferb">我是买方,向卖方传递信息</button>
</div>
<div id="seller">
    <h1>显示买方消息:{{ message2 }}</h1>
    <button @click="transfers">我是卖方,向买方传递信息</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
//此处需引用mitt.js文件!!!
<script>
    const bus = mitt()
    //买方
    const buyer = Vue.createApp({
        data() {
            return {
                message1: ''
            }
        },
        methods: {
            transferb() {
                //用emit触发事件传值
                bus.emit('on-message1', '来自买方的信息')
            }
        },
        mounted(){
            //监听
            bus.on('on-message2', (msg) => {//(msg)相当于function (msg)
                this.message1 = msg
            })
        }
    })
    buyer.mount('#buyer')
    //卖方
    const seller = Vue.createApp({
        data() {
            return {
                message2: ''
            }
        },
        methods: {
            transfers() {
                //用emit触发事件传值
                bus.emit('on-message2', '来自卖方的信息')
            }
        },
        mounted(){
            //监听
            bus.on('on-message1', (msg) => {//(msg)相当于function (msg)
                this.message2 = msg
            })
        }
    })
    seller.mount('#seller')
</script>

  • 27
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值