【09】Vue基础:组件


基本示例

  • 在Vue中,组件是一个很强大的功能,组件可以扩展HTML元素,封装可重用的代码。比如在页面中的某一部分需要在多个场景中使用,那么我们可以将需要部分提取出来,从而提高代码的复用率。
  • 所有的Vue组件都是Vue的实列,因此它可以接受Vue中的所有的生命周期钩子。
    示例代码:
    <!DOCTYPE html>
    <html>
      <head>
          <meta charset="utf-8">
          <title>组件基础</title>
          <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
      </head>
      <body>
          <div id="app">
              <v-btn></v-btn>
              <hr />
              <v-btn></v-btn>
          </div>
          <script type="text/javascript">
              // 自定义组件
              Vue.component('v-btn', {
                  data: function () {
                      return {
                          count: 0
                      }
                  },
                  template: `
                      <div>
                          <h1>{{ count }}</h1>
                          <button @click='count++'>点击我试试</button>
                      </div>
                  `
              })
              // 实例化
              new Vue({ el: '#app' });
          </script>
      </body>
    </html>

data 必须是一个函数

在上面的示例代码中,我们可以看到有一段代码是这样的:

data: function () {
    return {
        count: 0
    }
},

那么,为什么data中的数据需要使用函数return回来,而不直接使用Object形式呢?

  • vue组件中data值不能为对象,因为对象是引用类型,组件可能会被多个实例同时引用。如果data值为对象,将导致多个实例共享一个对象,其中一个组件改变data属性值,其它实例也会受到影响。
  • data为函数,通过return 返回对象的拷贝,致使每个实例都有自己独立的对象,实例之间可以互不影响的改变data属性值。

例子一【引用类型】:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>例子</title>
    </head>
    <body>
        <div id="app"></div>
        <script>
            const fn = function() {};
            fn.prototype.data = { a: 1 };
            const demo01 = new fn();
            const demo02 = new fn();
            demo01.data.a = 5;
            document.getElementById('app').innerHTML = `
                <div>[demo02.data.a]:${demo02.data.a}</div>
            `;

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

打开浏览器运行上面的代码:可以看到页面显示内容为:

[demo02.data.a]:5

为什么demo02.data.a不是1呢?
因为每个组件的data都在内存的同一个地址中,一个数据改变了其他也会跟着改变。

例子二【函数】:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>例子</title>
    </head>
    <body>
        <div id="app"></div>
        <script>
            const fn = function() {
                this.data = this.data();
            };
            fn.prototype.data = function() {
                return { a: 1 };
            };
            const demo01 = new fn();
            const demo02 = new fn();
            demo01.data.a = 5;
            document.getElementById('app').innerHTML = `
                <div>[demo02.data.a]:${demo02.data.a}</div>
            `;

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

打开浏览器运行上面的代码:可以看到页面显示内容为:

[demo02.data.a]:1

为什么demo02.data.a1呢?
因为data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响。

通过 Prop 向子组件传递数据

模版写法:

Vue.component('v-h1', {
    props: ['msg'],
    template: `<h1>{{ msg }}</h1>`
});

示例代码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>组件基础</title>
        <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
    </head>
    <body>
        <div id="app">
            <v-h1 msg="我是标题"></v-h1>
        </div>
        <script type="text/javascript">
            Vue.component('v-h1', {
                props: ['msg'],
                template: `<h1>{{ msg }}</h1>`
            });
            new Vue({ el: '#app' });
        </script>
    </body>
</html>

监听子组件事件

在我们开发组件时,它的一些功能可能要求我们和父级组件进行沟通。
示例代码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>组件基础</title>
        <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
    </head>
    <body>
        <div id="app">
            <div :style="{ fontSize: fontSize + 'em' }">
                <v-p @big-font="fontSize++"></v-p>
            </div>
        </div>
        <script type="text/javascript">
            Vue.component('v-p', {
                template: `
                    <div>
                        <p>我是标题</p>
                        <button @click="$emit('big-font')">放大文字</button>
                    </div>
                `
            });
            new Vue({ 
                el: '#app',
                data() {
                    return {
                        fontSize: 1
                    }
                }
            });
        </script>
    </body>
</html>

通过上面的代码可以发现,子组件可以通过调用内建的$emit方法并传入事件名称来触发一个事件,代码片段:

<!-- 子组件 -->
<button @click="$emit('big-font')">放大文字</button>

<!-- 父组件 -->
<v-p @big-font="fontSize++"></v-p>

使用事件抛出一个值

示例代码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>组件基础</title>
        <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
    </head>
    <body>
        <div id="app">
            <div :style="{ fontSize: fontSize + 'em' }">
                <v-p @big-font="fontSize += $event"></v-p>
            </div>
        </div>
        <script type="text/javascript">
            Vue.component('v-p', {
                template: `
                    <div>
                        <p>我是标题</p>
                        <button @click="$emit('big-font', 0.1)">放大文字</button>
                    </div>
                `
            });
            new Vue({ 
                el: '#app',
                data() {
                    return {
                        fontSize: 1
                    }
                }
            });
        </script>
    </body>
</html>

代码片段:

<!-- 子组件 -->
<button @click="$emit('big-font', 0.1)">放大文字</button>

<!-- 父组件 -->
<v-p @big-font="fontSize += $event"></v-p>

跟上面的例子做对比可以发现,使用事件抛出一个值时,只需在$emit上添加多一个参数即可,接收时使用$event进行接收。

通过插槽分发内容

在编写组件的过程中,有时我们希望组件里面能自己定义一些HTML元素,这时我们可以使用<slot>实现。
示例代码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>组件基础</title>
        <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
    </head>
    <body>
        <div id="app">
            <v-demo>
                <p style="color: red;">我是内容</p>
            </v-demo>
        </div>
        <script type="text/javascript">
            Vue.component('v-demo', {
                template: `
                    <div>
                        <h1>我是标题</h1>
                        <slot></slot>
                    </div>
                `
            });
            new Vue({ el: '#app' });
        </script>
    </body>
</html>

定义组件名

两种方式:

// 使用 kebab-case
Vue.component('my-component-name', { /* ... */ })

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

组件的注册

组件的注册分为全局注册局部注册两种。

全局注册

代码模版:

Vue.component('ComponentName', {...});
new Vue({ el: '#app' })

在组件基础中的所有代码都为全局注册。

局部注册

代码模版:

// 编写 v-demo 组件
var VDemo = {
    template: `<h1>我是标题</h1>`
};

// 注册 v-demo 组件
new Vue({
    el: '#app',
    components: {
        'v-demo': VDemo
    }
});

示例代码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>深入了解组件</title>
        <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
    </head>
    <body>
        <div id="app">
            <v-demo></v-demo>
        </div>
        <script type="text/javascript">
            var VDemo = {
                template: `<h1>我是标题</h1>`
            };
            new Vue({
                el: '#app',
                components: {
                    'v-demo': VDemo
                }
            });
        </script>
    </body>
</html>

全局注册和局部注册的区别

全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。


文章作者: 技术潘
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 技术潘 !
  目录