VUE自定义组件

简介

有时候有一组html结构的代码,并且这个上面可能还绑定了事件。然后这段代码可能有多个地方都被使用到了,如果都是拷贝来拷贝去,很多代码都是重复的,包括事件部分的代码都是重复的。那么这时候我们就可以把这些代码封装成一个组件,以后在使用的时候就跟使用普通的html元素一样,拿过来用就可以了。

基本使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuedemo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>

<div id="app">
    <button-counter></button-counter>
    <button-counter></button-counter>
    <button-counter></button-counter>
</div>
<script>
    Vue.component('button-counter', {
        data: function () {
            return {
                count: 0
            }
        },
        template: '<button v-on:click="count++">点击了{{ count }}次</button>'
    });
    let vm = new Vue({
        el: "#app",
        data: {}
    });
</script>
</body>
</html>

 

以上我们创建了一个叫做button-counter的组件,这个组件实现了能够记录点击了多少次按钮的功能。后期如果我们想要使用,就直接通过button-counter使用就可以了。然后因为组件是可复用的Vue实例,所以它们与new Vue接收相同的选项,例如datacomputedwatchmethods以及生命周期钩子等。仅有的例外是像el这样根实例特有的选项。另外需要注意的是:组件中的data必须为一个函数!

官网文档例子:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuedemo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="components-demo">
    <button-counter></button-counter>
</div>

<script>

    // 定义一个名为 button-counter 的新组件
    Vue.component('button-counter', {
        data: function () {
            return {
                count: 0
            }
        },
        template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
    });

    //组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是 <button-counter>。
    // 我们可以在一个通过 new Vue 创建的 Vue 根实例中,把这个组件作为自定义元素来使用:
    new Vue({
        el: '#components-demo',

    });

</script>
</body>
</html>

给组件添加属性

像原始的html元素都有自己的一些属性,而我们自己创建的组件,也可以通过prop来添加自己的属性。这样别人在使用你创建的组件的时候就可以传递不同的参数了。示例代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuedemo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>

<div id="app">
    <blog-post :title="blog.title" v-for="blog in blogs"></blog-post>
</div>
<script>
    Vue.component('blog-post', {
        props: ['title'],
        template: '<h3>{{ title }}</h3>'
    });
    new Vue({
        el: "#app",
        data: {
            blogs: [
                {"title": "想想霸哥怎么做", "id": 1},
                {"title": "霸王计划", "id": 2},
                {"title": "如何学好Vue", "id": 3},
            ]
        }
    });
</script>
</body>
</html>

单一根元素:

如果自定义的组件中,会出现很多html元素,那么根元素必须只能有一个,其余的元素必须包含在这个根元素中。比如以下是一个组件中的代码,会报错:

<h3>{{ title }}</h3>
<div v-html="content"></div>

我们应该改成:

<div class="blog-post">
  <h3>{{ title }}</h3>
  <div v-html="content"></div>
</div>

组件中自定义事件

子组件中添加事件跟之前的方式是一样的,然后如果发生某个事件后想要通知父组件,那么可以使用this.$emit函数来实现。示例代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuedemo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id='app'>
    <blog-item v-for="blog in blogs" :blog="blog" @check-changed="checkChanged"></blog-item>
    <h1>选中的博客:</h1>
    <div v-for="blog in selected_blogs">
        {{blog.title}}
    </div>
</div>
<script>
    Vue.component("blog-item", {
        props: ['blog'],
        template: `
        <div>
          <span>{{blog.title}}</span>
          <input type="checkbox" @click="onCheck">
        </div>
      `,
        methods: {
            onCheck() {
                this.$emit("check-changed", this.blog)
            }
        }
    });
    new Vue({
        el: '#app',
        data: {
            blogs: [{
                title: "如何学好Vue?",
                id: "1"
            }, {
                title: "前后端分离项目?",
                id: "2"
            }],
            selected_blogs: []
        },
        methods: {
            checkChanged(blog) {
                // indexOf:获取某个元素在数组中的位置,如果返回值为非负数,那么就是存在,就是下标
                // 否则,代表这个元素在数组中不存在
                let index = this.selected_blogs.indexOf(blog);
                if (index >= 0) {
                    this.selected_blogs.splice(index, 1)
                } else {
                    this.selected_blogs.push(blog)
                }
            }
        }
    })
</script>
</body>
</html>

需要注意的是,因为html中大小写是不敏感的,所以在定义子组件传给父组件事件名称的时候,不要使用myEvent这种驼峰命名法,而是使用my-event这种规则。

上述示例代码调用关系如下:

 

 运行结果于勾选后展示结果:

 

自定义组件v-model

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuedemo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id='app'>
    <Stepper v-model="goods_count"></Stepper>
</div>
<script>
    // 计步器
    Vue.component("Stepper", {
        props: ['count'],
        // model:用来配置v-model的表现形式
        model: {
            event: "count-changed",
            prop: "count"
        },
        template: `
        <div>
          <button @click="substract">-</button>
          <span>{{count}}</span>
          <button @click="add">+</button>
        </div>
      `,
        methods: {
            substract() {
                // 这个里面不需要修改this.count的值,只要把结果传出去就可以了
                this.$emit("count-changed", this.count - 1)
            },
            add() {
                this.$emit("count-changed", this.count + 1)
            }
        }
    });

    new Vue({
        el: '#app',
        data: {
            goods_count: 0
        },
        watch: {
            goods_count: function (newValue, oldValue) {
                console.log('==========');
                console.log(newValue);
                console.log(oldValue);
                console.log('==========');
            }
        }
    })
</script>
</body>
</html>

插槽

我们定义完一个组件后,可能在使用的时候还需要往这个组件中插入新的元素或者文本。这时候就可以使用插槽来实现。示例代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuedemo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="app">
    <navigation-link url="/profile/">
        个人中心
    </navigation-link>
</div>
<script>
    Vue.component('navigation-link', {
        props: ['url'],
        template: `
        <a v-bind:href="url" class="nav-link">
            <slot></slot>
        </a>
        `
    });
    new Vue({
        el: "#app"
    });
</script>
</body>
</html>

当组件渲染的时候,<slot></slot>将会被替换为“个人中心”。插槽内可以包含任何模板代码,包括HTML

    <navigation-link url="/profile">
        <span style="color: red">个人中心</span>
    </navigation-link>

如果<navigation-link>没有包含一个<slot>元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。

作用域

通过外面传给组件的变量,在以后使用插槽的时候是不能使用的。比如以上url只能在navigation-link中使用,但是后面使用插槽的时候不能使用。比如

<navigation-link url="/profile">
  Clicking here will send you to: {{ url }}
  <!--
  这里的 `url` 会是 undefined,因为 "/profile" 是
  _传递给_ <navigation-link> 的而不是
  在 <navigation-link> 组件*内部*定义的。
  -->
</navigation-link>

插槽默认值

有时候在使用组件的时候,插槽中绝大部分情况是一种元素。那么我们就可以给插槽提供一个默认值,然后后面如果不想使用这个默认值的时候,就只需要提供自己定义的值就可以了。比如有一个叫做submit-button的组件,代码如下:

<button type="submit">
  <slot>提交</slot>
</button>

然后在使用这个组件的时候,可以直接<submit-button></submit-button>,默认在里面就会显示“提交”文字。如果想要在使用的时候显示其他文字,那么也可以通过<submit-button>保存</submit-button>来实现。

命名插槽:

自定义组件中可以有多个插槽,这时候就需要通过名字来进行区分了。其实如果没有指定名字,默认是有一个名字叫做default的。比如我们有一个名叫container的自定义组件:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

以后在使用这个组件的时候使用v-slot:插槽名的方式来加载不同的数据:

<container>
    <template v-slot:header>
        这是头部信息
    </template>
    这是主要部分的信息
    <template v-slot:footer>
        这是网页尾部信息
    </template>
</container>

插槽作用域:

默认在插槽中的代码只能使用全局Vue中的属性,如果想要使用自定义组件中的属性,那么需要在定义slot的时候使用v-bind来进行绑定。示例代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuedemo</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="app">
    <sub-nav v-slot="slotProps">
        当前点击:{{slotProps.index}}
    </sub-nav>
</div>
<script>
    Vue.component('sub-nav', {
        props: ['url'],
        data: function () {
            return {
                navs: ['网络设置', '路由设置', '设备管理'],
                index: 0
            }
        },
        methods: {
            indexBtnClick: function (index) {
                this.index = index;
            }
        },
        template: `
        <div class="container">
            <button v-for="(nav,index) in navs" @click="indexBtnClick(index)" v-bind:key="index">{{nav}}</button>
            <slot v-bind:index="index"></slot>
        </div>
        `
    });
    new Vue({
        el: "#app"
    });
</script>
</body>
</html>

  

posted @ 2020-06-05 10:35  0bug  阅读(438)  评论(0编辑  收藏  举报