前端基础之《Vue(5)—组件基础(1)》

发布于:2025-04-21 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、什么是组件化

1、理解组件化
组件是HTML的扩展,使用粒度较小的HTML元素封装成粒度更大的标签(Vue组件)。可以实现快速开发、代码复用、提升可维护性。
相当于盖房子,用预制板,不是用一块块砖,一天可以盖好几层。

对Vue组件来讲,最重要的是视图结构,所以我们在封装Vue组件时,可以使用大多数的Vue选项。
视图结构是更小粒度的HTML标签。

2、如何自定义组件
一个组件,你可以理解成是由一组选项构成的,在封装自定义组件时,你可以使用大多数的Vue选项属性,比如:data、template、methods等。
组件封装后,必须要注册(局部注册,或,全局注册)才能在Vue的地盘中使用,注册自定义组件时,组件名称必须由多个单词用中划线连接。
对一个组件来讲,最重要的选项是template选项,用于指定组件的视图结构,在视图结构中你可以使用任意指令。
对一个组件来讲,你可以使用props选项,用于接收父组件传递过来的自定义属性,在组件内部用this访问它们。
对一个组件来讲,你可以使用this.$emit('自定义事件','数据')触发父组件给的自定义事件,并回传数据给父组件。

3、如何进行组件注册
全局注册:Vue.component('xx-yy', '原材料选项')。全局注册的组件,一次注册,在任何其它组件中都可以使用。
局部注册:components: {'xx-yy': '原材料选项'}。局部注册的组件,只能在当前组件作用域中使用。

4、vue的根组件

// 根组件(被new的,实例化)
const app = new Vue({
    el: '#app'
})

5、组件化的三大技术
自定义属性
自定义事件
自定义插槽

vue中有三个指令有简写:
v-bind:“:”
v-on:“@”
v-slot:#default
正好对应上面三个技术。

二、封装一个评分组件

例子代码:

<html>
<head>
    <title>组件基础-1</title>
    <style>
        .score {
            display: inline-block;
        }
        .score>span {
            display: inline-block;
            width: 50px;
            height: 50px;
            background: url(./assets/star.png) center center / 50px 50px;
            cursor: pointer;
        }
        .score>span.on {
            background: url(./assets/star-on.png) center center / 50px 50px;
        }
    </style>
</head>
<body>
    <div id="app">
        <!-- v-bind:childnum,自定义属性绑定一个父组件变量,要在子组件的props中接收的 -->
        <!-- v-on:abc,自定义事件,用于接收子组件回传回来的数据 -->
        <score-1 v-bind:childnum="num" v-on:abc="getChildNum"></score-1>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>

    <!-- 评分组件的视图结构 -->
    <script type="text/x-template" id="score">
        <!-- 指定视图结构 -->
        <div class="score">
            <span v-for='(i, index) in 5' v-bind:class='{on: childnum>=i}' v-on:click='changexingxing(i)'></span> <!-- 这是一个布尔值,i是1到5,只有num大于等于i的才显示 -->
        </div>

    </script>

    <script>
        // 组件原材料(视图、接收父组件传递过来的自定义属性)
        const score = {
            // 指定当前组件的视图结构
            template: '#score',
            // 用于接收父组件传递过来的自定义属性,用this访问它们
            props: {
                // 接收childnum,指定数字类型,默认为0
                childnum: {type: Number, default: 0}
            },
            // 生命周期时候讲
            mounted() {
                console.log('score num', this.childnum)
            },
            methods: {
                changexingxing(i) {
                    console.log('---clicked', i)
                    // 在这里能让父组件中的num更新,那么分数界面就会变化

                    // 触发自定义事件abc,同时回传i回去
                    this.$emit('abc', i)
                }
            }
        }

        // 根组件(被new的,实例化)
        const app = new Vue({
            el: '#app',
            data: {
                num: 0
            },
            // 局部注册组件
            components: {
                // 组件名字必须是2个以上的单词(可以用横杠分割)
                'score-1': score
            },
            methods: {
                getChildNum(ev) {
                    console.log('---收到了score子组件回传的数据', ev)
                    this.num = ev
                }
            }
        })
    </script>

</body>
</html>

1、自己写一个组件(步骤分解)

(1)定义组件的视图结构
固定写法

<!-- 评分组件的视图结构 -->
<script type="text/x-template" id="score">
    <!-- 指定视图结构 -->
    <div class="score">
        <span v-for='(i, index) in 5' v-bind:class='{on: childnum>=i}' v-on:click='changexingxing(i)'></span> <!-- 这是一个布尔值,i是1到5,只有num大于等于i的才显示 -->
    </div>

</script>

(2)用一个const常量关联这个视图结构的id
template: '#score'

// 组件原材料(视图、接收父组件传递过来的自定义属性)
const score = {
    // 指定当前组件的视图结构
    template: '#score',
    // 用于接收父组件传递过来的自定义属性,用this访问它们
    props: {
        // 接收childnum,指定数字类型,默认为0
        childnum: {type: Number, default: 0}
    },
    // 生命周期时候讲
    mounted() {
        console.log('score num', this.childnum)
    },
    methods: {
        changexingxing(i) {
            console.log('---clicked', i)
            // 在这里能让父组件中的num更新,那么分数界面就会变化

            // 触发自定义事件abc,同时回传i回去
            this.$emit('abc', i)
        }
    }
}

(3)注册组件
在app根组件的components选项中关联这个常量

// 根组件(被new的,实例化)
const app = new Vue({
    el: '#app',
    data: {
        num: 0
    },
    // 局部注册组件
    components: {
        // 组件名字必须是2个以上的单词(可以用横杠分割)
        'score-1': score
    },
    methods: {
        getChildNum(ev) {
            console.log('---收到了score子组件回传的数据', ev)
            this.num = ev
        }
    }
})

(4)定义组件名称
'score-1': score

(5)像HTML标签一样使用组件名称

<score-1 v-bind:childnum="num" v-on:abc="getChildNum"></score-1>

在自定义组件名称标签里的属性,都是自定义属性。
在自定义组件名称标签里的事件,都是自定义事件。

2、自定义属性

(1)在组件名称标签上自定义一个属性名称,绑定父组件的变量
v-bind:childnum="num"

(2)子组件里用props选项接收父组件的变量

// 用于接收父组件传递过来的自定义属性,用this访问它们
props: {
    // 接收childnum,指定数字类型,默认为0
    childnum: {type: Number, default: 0}
},

(3)在视图里使用这个子组件的自定义变量

<span v-for='(i, index) in 5' v-bind:class='{on: childnum>=i}' v-on:click='changexingxing(i)'></span>

3、自定义事件

(1)在组件名称标签上自定义一个事件abc,关联父组件的一个方法
v-on:abc="getChildNum"

(2)在子组件视图里调用子组件自己的方法,触发自定义事件,传递参数

changexingxing(i) {
    console.log('---clicked', i)
    // 在这里能让父组件中的num更新,那么分数界面就会变化

    // 触发自定义事件abc,同时回传i回去
    this.$emit('abc', i)
}

(3)父组件定义方法接收参数

getChildNum(ev) {
    console.log('---收到了score子组件回传的数据', ev)
    this.num = ev
}

4、需要注意的点
这个变量、方法是属于哪个组件的,就定义在哪里。
父组件向子组件传递参数。
子组件里修改父组件的变量。
子组件通过自己视图结构里的点击事件,获取数据。然后触发自定义事件(调用父组件方法),将数据传给父组件,修改父组件里的变量。然后子组件已经通过自定义属性绑定了父组件的这个变量,获取到这个变量值,再修改子组件视图结构里的展示。

三、关于组件间关系与通信

1、约定:在MVVM框架,当我们谈论“组件”这个概念时,通常指的是自定义组件。比如当在A组件的视图结构中使用到了B组件,这就形成了组件关系(父子关系)。那么A就是B的父组件,B就是A的子组件。当组件足够多的时候,组件间关系就会形成“组件树”。

2、通信:在Vue中,所谓“通信”就是组件之间的数据交互。父组件向子组件通信,使用自定义属性,在子组件中使用props接收。子组件向父组件通信,使用自定义事件,在子组件中使用this.$emit('自定义事件','数据')回传。

注意:在视图模板中,不要用this,因为视图模板是要被vue编译器编译的,它是从上下文拿变量的,而不是通过this拿的。

四、组件复用的问题

1、slot标签
在子组件的视图结构里加入slot标签,类似于一个占位的功能

<script type="text/x-template" id="score">
    <!-- 指定视图结构 -->
    <div class="score">
        <slot name='default'></slot>
        <span v-for='(i, index) in 5' v-bind:class='{on: value>=i}' v-on:click='$emit("input", i)'></span> <!-- 这是一个布尔值,i是1到5,只有num大于等于i的才显示 -->
        <slot name='xxx'></slot>
    </div>

</script>

添加“配送速度”文本

<div id="app">
    <!-- v-bind:childnum,自定义属性绑定一个父组件变量,要在子组件的props中接收的 -->
    <!-- v-on:abc,自定义事件,用于接收子组件回传回来的数据 -->
    <score-1 v-bind:childnum="num" v-on:abc="getChildNum">
        配送速度:
    </score-1>
</div>

显示效果

如果把文本换成其他的,视图结构里会自动变。

默认添加到default的插槽中。

五、简化自定义属性、自定义事件写法

<html>
<head>
    <title>组件基础-2</title>
    <style>
        .score {
            display: inline-block;
        }
        .score>span {
            display: inline-block;
            width: 50px;
            height: 50px;
            background: url(./assets/star.png) center center / 50px 50px;
            cursor: pointer;
        }
        .score>span.on {
            background: url(./assets/star-on.png) center center / 50px 50px;
        }
    </style>
</head>
<body>
    <div id="app">
        <!-- 在指令表达式中使用$event拿到事件对象 -->
        <score-1 v-bind:value="num" v-on:input='num = $event'>
            配送速度:
        </score-1>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>

    <!-- 评分组件的视图结构 -->
    <script type="text/x-template" id="score">
        <!-- 指定视图结构 -->
        <div class="score">
            <slot name='default'></slot>
            <span v-for='(i, index) in 5' v-bind:class='{on: value>=i}' v-on:click='$emit("input", i)'></span> <!-- 这是一个布尔值,i是1到5,只有num大于等于i的才显示 -->
            <slot name='xxx'></slot>
        </div>

    </script>

    <script>
        // 组件原材料(视图、接收父组件传递过来的自定义属性)
        const score = {
            // 指定当前组件的视图结构
            template: '#score',
            // 用于接收父组件传递过来的自定义属性,用this访问它们
            props: {
                value: {type: Number, default: 0}
            }
        }

        // 根组件(被new的,实例化)
        const app = new Vue({
            el: '#app',
            data: {
                num: 0
            },
            // 局部注册组件
            components: {
                // 组件名字必须是2个以上的单词(可以用横杠分割)
                'score-1': score
            }
        })
    </script>

</body>
</html>

六、简化组件注册写法
改成全局注册

<html>
<head>
    <title>组件基础-3</title>
    <style>
        .score {
            display: inline-block;
        }
        .score>span {
            display: inline-block;
            width: 50px;
            height: 50px;
            background: url(./assets/star.png) center center / 50px 50px;
            cursor: pointer;
        }
        .score>span.on {
            background: url(./assets/star-on.png) center center / 50px 50px;
        }
    </style>
</head>
<body>
    <div id="app">
        <score-1 v-bind:value="num" v-on:input='num = $event'>
            配送速度:
        </score-1>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>

    <script>
        // 评分组件
        Vue.component('score-1',{
            template: `
            <div class="score">
                <slot name='default'></slot>
                <span v-for='(i, index) in 5' v-bind:class='{on: value>=i}' v-on:click='$emit("input", i)'></span>
                <slot name='xxx'></slot>
            </div>
            `,
            props: {
                value: {type: Number, default: 0}
            }
        })
        
        const app = new Vue({
            el: '#app',
            data: {
                num: 0
            }
        })
    </script>

</body>
</html>


网站公告

今日签到

点亮在社区的每一天
去签到