vue(五) – vue.js组件

组件用于封装页面的部分功能,将功能的结构样式逻辑代码封装为整体。

提高功能的复用性与可维护性,更好的专注于业务逻辑。

组件使用时为自定义 HTML 标签形式,通过组件名作为自定义标签名。

image.png

组件注册

全局注册

全局注册的组件在注册后可以用于任意实例或组件中。
image.png

注意:全局注册必须设置在根 Vue 实例创建之前

<body>
  <div id="app">
    <p>这是p标签</p>
    <my-component></my-component>
  </div>
  
  <script src="lib/vue.js"></script>
  <script>
    Vue.component('my-component', { 
      template: '<div>这是我们全局注册的组件</div>'
    });

    // 根实例
    new Vue({
      el: '#app',
      data: {

      }
    })
  </script>
</body>
复制代码

image.png

组件基础

本质上,组件是可复用的 Vue 实例,所以它们可与 new Vue 接收相同的选项,例如 datamethods以及生命周期钩子等。

注意 仅有的例外是像 el 这样根实例特有的选项。因为根实例是需要挂载到页面上一个元素中使用的, 而组件是被根实例或被其他组件使用的, 不需要挂载到页面中, 所以没有el.

组件命名规则

组件具有两种命名规则:

  • kebab-case:'my-component'
  • PascalCase:'MyComponent'

image.png

注意:无论采用哪种命名方式,在 DOM 中都只有 kebab-case 可以使用

<body>
  <div id="app">
    <my-com-a></my-com-a>
    <!-- <MyComA></MyComA> 会报错-->
    <my-com-b></my-com-b>
    <!-- <MyComB></MyComB> 会报错-->
  </div>
  <script src="lib/vue.js"></script>
  <script>
    // kebab-case 进行注册
    Vue.component('my-com-a',{
      template: "<div>a组件内容</div>"
    })

    // PascalCase 进行注册
    Vue.component('MyComB', {
      template: '<div>b组件的内容</div>'
    });

    new Vue({
      el: '#app',
      data: {

      }
    })
  </script>
</body>
复制代码

image.png

template 选项

template 选项用于设置组件的结构,最终被引入根实例或其他组件中。

image.png

注意:组件必须只有一个根元素。也就是说不能在<div>同级的位置再设置其他元素了.

<body>
  <div id="app">
    <my-com-a></my-com-a>
  </div>
  <script src="lib/vue.js"></script>
  <script>
    Vue.component('MyComA', {
      template: `
        <div>
          这是组件 A 的内容: {{ 1 + 2 * 3 }}
        </div>
      `
    });

    new Vue({
      el: '#app',
      data: {
        
      }
    });
  </script>
</body>
复制代码

image.png

data 选项

data 选项用于存储组件的数据,与根实例不同,组件的 data 选项必须为函数数据设置在返回值对象中。

这种实现方式是为了确保每个组件实例可以维护一份被返回对象的独立的拷贝,不会相互影响。因为函数的作用域只在它内部有效.

image.png

ES6简写方式:

image.png

<body>
  <div id="app">
    <my-com-a></my-com-a>
  </div>
  <script src="lib/vue.js"></script>
  <script>
    Vue.component('MyComA', {
      template: `
        <div>
          <h3>{{ title }}</h3>
          <p>{{ content }}</p>
        </div>
      `,
      data () {
        return {
          title: '这是组件标题',
          content: '这是组件内容'
        }
      }
    });

    new Vue({
      el: '#app',
      data: {

      }
    });
  </script>
</body>
复制代码

局部注册

局部注册的组件只能用在当前实例或组件中。
有两种注册方式:

1.直接在Vue实例中添加

image.png

<body>
  <div id="app">
    <my-com-a></my-com-a>
    <my-com-b></my-com-b>
  </div>

  <div id="app2">
    <my-com-a></my-com-a>
    <my-com-b></my-com-b>
  </div>
  <script src="lib/vue.js"></script>
  <script>
    new Vue({
      el: '#app',
      data: {

      },
      components: {
        //第一种命名方式, 必须加引号, 否则不支持-连接
        'my-com-a': {
          template: `
            <div>
              <h3>{{ title }}</h3>
              <p>{{ content }}</p>
            </div>
          `,
          data () {
            return {
              title: '组件 A 标题',
              content: '组件 A 内容'
            }
          }
        },
        //第二种命名方式
        MyComB: {
          template: `
            <div>
              <h3>{{ title }}</h3>
              <p>{{ content }}</p>
            </div>
          `,
          data () {
            return {
              title: '组件 B',
              content: '组件 B 内容'
            }
          }
        }
      }
    });

    new Vue({
      el:"#app2"
    })
  </script>
</body>
复制代码

image.png

2.单独配置组件的选项对象

更便于维护

image.png

ES6简写方式: (兼容性不如上面的方式)

image.png

<body>
  <div id="app">
    <my-component-a></my-component-a>
    <my-component-b></my-component-b>
  </div>
  <script src="lib/vue.js"></script>
  <script>
    // 组件 A 的选项对象
    var MyComponentA = {
      template: `
        <div>
          <h3>{{ title }}</h3>
          <p>{{ content }}</p>
        </div>
      `,
      data () {
        return {
          title: '组件 A 标题',
          content: '组件 A 内容'
        }
      }
    };

    // 组件 B 的选项对象
    var MyComponentB = {
      template: `
        <div>
          <h3>{{ title }}</h3>
          <p>{{ content }}</p>
        </div>
      `,
      data () {
        return {
          title: '组件 B',
          content: '组件 B 内容'
        }
      }
    }

    new Vue({
      el: '#app',
      data: {

      },
      components: {
        'my-component-a': MyComponentA,  
        MyComponentB
      }
    });
  </script>
</body>
复制代码

组件通信

在组件间传递数据的操作,称为组件通信。根据参与组件的不同, 传递方式不同.

父组件向子组件传值

通过子组件的 props 选项接收父组件的传值. 接受后可以直接在template中使用.

注意props 不要与 data 存在同名属性, 否则会出现覆盖问题.
image.png

父组件设置方式如下:

image.png

image.png

  • 没有加冒号的title是静态传递的, 没法更改.

  • 加了冒号的title是动态传递的, 文字要加引号

<body>
    <div id="app">
        <my-component-a title="这是静态标题" content="这是静态内容"></my-component-a>
        <!-- 绑定动态内容, 常用 -->
        <my-component-a :title="item.title" :contnet="item.content"></my-component-a>
    </div>
    <script src="lib/vue.js"></script>
    <script>
        Vue.component("my-component-a",{
            props: ['title','content'],
            template:`
                <div>
                    <h3>{{ title }}</h3>
                    <p> {{ content }}</p>
                </div>
            `
        })

        new Vue({
            el:"#app",
            data:{
                item: {
                    title: "标题",
                    content: "内容"
                }
            }
        })
    </script>
</body>
复制代码

image.png

Props 命名规则

建议 prop 命名使用 camelCase,父组件绑定时使用 kebab-case

image.png

image.png

<body>
  <div id="app">
    <!-- 通过 v-for 遍历数据 items,创建组件并生成内容 -->
    <demo-item v-for="item in items" :key="item.title"
    :item-title="item.title" :item-content="item.content"></demo-item>
  </div>
  <script src="lib/vue.js"></script>
  <script>
    Vue.component("demoItem",{
      props: ["itemTitle","itemContent"],
      template: `
        <div>
          <h3>{{ itemTitle }}</h3>
          <p> {{ itemContent }} </p>
        </div>
      `
    })

    new Vue({
      el: '#app',
      data: {
        // 准备给子组件使用的数据
        items: [
          {
            title: '示例标题1',
            content: '示例内容1'
          },
          {
            title: '示例标题2',
            content: '示例内容2'
          },
          {
            title: '示例标题3',
            content: '示例内容3'
          },
        ]
      }
    })
  </script>
</body>
复制代码

image.png

单向数据流

父子组件间的所有 prop 都是单向下行绑定的。父组件的数据修改会影响到子组件, 但子组件中的数据修改不会影响到父组件.

如果子组件要处理 prop 数据,应当存储在 data 中(或使用计算属性)后操作。

image.png

注意:
如果 prop 为数组对象时,子组件操作将会影响到父组件的状态。因为是一种引用的形式, 获取了地址.

解决方法:

  1. 将数据存在data中再进行操作
  2. 避免传入整个数组或者对象, 而是只传入对象的某个属性值.
<body>
  <div id="app">
    <my-component
      :initial-title="title"
      :obj="obj"
    ></my-component>
  </div>
  <script src="lib/vue.js"></script>
  <script>
    Vue.component('my-component', {
      props: ['initialTitle', 'obj'],
      template: `
        <div>
          {{ title }}
          <button @click="fn">按钮</button>
        </div>
      `,
      data () {
        return {
          title: this.initialTitle
          // title: this.initialTitle = "这是新的 标题"
        }
      },
      methods: {
        fn () {
          // this.title = '这是新的标题';
          // this.initialTitle = '这是新的标题'; // 不会影响父组件
          this.obj.name = 'jack'; //会导致root中的名称也改为jack

        }
      }
    });

    new Vue({
      el: '#app',
      data: {
        title: '这是示例标题',
        obj: {
          name: 'william',
          age: 18
        }
      }
    });
  </script>
</body>
复制代码

image.png

Props 类型

Prop 可以设置类型检查,这时需要将 props 更改为一个带有验证需求的对象,并指定对应类型。

  • 在后面设置上对应的构造函数就可以了. StringArray都是构造函数.

  • 如果不限制类型, 可以设置为null或者undefined

image.png

image.png

prop 还可以同时指定多个类型,通过数组方式保存即可。

image.png

<body>
  <div id="app">
    <my-component
      :par-str="str"
      :par-num="num"
      :par-arr="arr"
      :par-obj="obj"
      :par-any="any"
      :par-data="str"
    ></my-component>
  </div>
  <script src="lib/vue.js"></script>
  <script>
    Vue.component('MyComponent', {
      // 如果要设置 props 的具体规则,需要更改为对象写法
      props: {
        parStr: String,
        parNum: Number,
        parArr: Array,
        parObj: Object,
        parAny: undefined, // null
        parData: [String, Boolean]
      },
      template: `
        <div>
          {{ parStr }}
          {{ parNum }}
          {{ parArr }}
          {{ parObj }}
          {{ parAny }}
          {{ parData }}
        </div>
      `
    })

    new Vue({
      el: '#app',
      data: {
        num: 100,
        str: 'abc',
        arr: [1, 2, 3],
        obj: {
          content1: '示例内容1',
          content2: '示例内容2'
        },
        any: [1, 2, 3]
      }
    });
  </script>
</body>
复制代码

Props 验证

当 prop 需要设置多种规则时,可以将 prop 的值设置为选项对象

  • 之前的类型检测功能通过type 选项设置。

image.png

其他的选项:

  • required 用于设置数据为必填项。(eg. required: true)

  • default 用于给可选项指定默认值,当父组件未传递数据时生效。(eg.default: 100)

  • requireddefault是不能同时作用于一个prop的, 因为required要求必须传值.

注意:当默认值为数组对象时,必须为工厂函数返回的形式。

image.png
或者:

image.png

  • validator 用于给传入的 prop 设置校验函数,return 值为 false 时 Vue.js 会发出警告。具体内容规则的检测.

image.png

注意:验证函数中无法使用实例的 data、methods 等功能。
props是在实例创建完之前做验证, 所以它没有办法使用data, methods之类的内容. 这时候的this指的是window, 而不是vue实例.

image.png

非 Props 属性

当父组件给子组件设置了属性,但此属性在 props 中没有接收,这时会自动绑定到子组件的根元素上。自定义指令也会绑定到根元素上.

比如说父元素中绑定了这些值:

image.png

而在子元素内部并没有接受这些值, 那么这些值会绑定到 <div>上.
image.png

特殊情况

  1. 如果组件根元素已经存在了对应属性,则新属性会替换原来的值

  2. classstyle 是例外,当内外都设置时,属性会自动合并

  3. 如果不希望继承父组件设置的属性,可以设置 inheritAttrs:false,但只适用于普通属性class 与 style 不受影响

例1

  <div id="app">
    <my-component
      data-index="3"
      :title="'新的标题内容'"
      style="height: 200px;"
      class="colorRed"
    ></my-component>
  </div>

    Vue.component('MyComponent', {
      template: `
      <div data-index="7"
           title="旧的title"
           class="abc" 
           style="width: 200px;">
        <p>这是组件的内容</p>
      </div>
      `
    });
复制代码

image.png

例2

  <div id="app">
    <my-component
      data-index="3"
      :title="'新的标题内容'"
      style="height: 200px;"
      class="colorRed"
    ></my-component>
  </div>
  
    Vue.component('MyComponent', {
      inheritAttrs: false,
      template: `
      <div data-index="7"
           title="旧的title"
           class="abc" 
           style="width: 200px;">
        <p>这是组件的内容</p>
      </div>
      `
    });
复制代码

image.png

子组件向父组件传值

子向父传值需要通过自定义事件实现。子组件进行数据修改的时候触发一个事件, 父组件监听.

商品为子组件,购物车为父组件,父组件需要统计商品个数,就需要在子组件个数变化时传值给父组件。

image.png

image.png

image.png

子组件数据变化时,通过 $emit() 触发自定义事件$emit()是vue实例的一个方法, 它内部可以传入一个名称, 就可以触发这个名称的自定义事件. 不需要手动触发.

  • 自定义事件名称建议使用 kebab-case。在父元素中写成 @count-change

image.png

父组件监听子组件的自定义事件,并设置处理程序。

  • 父组件监听到count-change事件触发后, 就将totalCount自加1

image.png

自定义事件传值

如果每次只告诉父组件触发了事件, 不太方便. 比如每次+5, 因此可以向父组件传值.

子组件触发事件时可以向父组件传值

  • $emit()的第一个参数是触发的事件名称, 第二个参数就是传递的值(对象或者变量等都可以)

方式一: 直接给自定义事件设置代码

image.png

父组件在监听事件时需要接收子组件传递的数据。
image.png

方式二: 通过处理函数

image.png

image.png

  • 这里的productCount就是$emit()传递的值.

组件与 v-model

v-model 用于组件时,需要通过 props自定义事件实现。因为组件之间是相互独立的.

  • v-model本身就有传入数据和数据接受的功能, 所以不需要写:value="...",所以子组件只需要用props接收就可以了.

image.png

image.png

  • value就是v-model绑定的那个值

  • 通过@input方式让输入框在进行输入的时候进行$emit()事件触发 (input事件是实时监控的,每次输入都会调用), $emit()不仅可以触发自定义事件还可以触发内部固有的一些事件

  • $event是事件对象, target是当前的输入框, value是输入的数据.

image.png

方法一:

<body>
  <div id="app">
    <p>输入框内容为:{{ iptValue }}</p>
    <com-input v-model="iptValue"></com-input>
  </div>
  <script src="lib/vue.js"></script>
  <script>
    var comInput = {
      props: ["value"],
      template: `
      <input type="text" 
      :value="value"
      @input="$emit('input', $event.target.value)">
      `
    }

    // 根实例
    new Vue({
      el: '#app',
      data: {
        iptValue: ''
      },
      components: {
        comInput
      }
    });
  </script>
</body>
复制代码

方法二:

<body>
  <div id="app">
    <p>输入框内容为:{{ iptValue }}</p>
    <com-input v-model="iptValue"></com-input>
  </div>
  <script src="lib/vue.js"></script>
  <script>
    var comInput = {
      props: ["value"],
      template: `
      <input type="text" 
      :value="value"
      @input="onInput">
      `,
      methods: {
        onInpt(event){
          this.$emit('input', $event.target.value)
        }
      }
    }

    // 根实例
    new Vue({
      el: '#app',
      data: {
        iptValue: ''
      },
      components: {
        comInput
      }
    });
  </script>
</body>
复制代码

image.png

非父子组件传值

非父子组件指的是兄弟组件完全无关的两个组件

兄弟组件传值

兄弟组件可以通过父组件进行数据中转.

<body>
  <div id="app">
    <!-- 父组件接收子组件A的数据 -->
    <com-a
      @value-change="value = $event"
    ></com-a>
    <!-- 父组件将数据传递给子组件B -->
    <com-b
      :value="value"
    ></com-b>
  </div>
  <script src="lib/vue.js"></script>
  <script>
    // 子组件A:发送数据
    Vue.component('ComA', {
      template: `
        <div>
          组件A的内容: {{ value }}
          <button
            @click="$emit('value-change', value)"
          >发送</button>
        </div>
      `,
      data () {
        return {
          value: '这是组件A中的数据'
        }
      }
    });

    // 子组件B:接收数据
    Vue.component('ComB', {
      props: ['value'],
      template: `
        <div>
          组件B接收到: {{ value }}
        </div>  
      `
    });


    // 根实例(父组件)
    new Vue({
      el: '#app',
      data: {
        // 用于数据中转
        value: ''
      }
    })
  </script>
</body>
复制代码

image.png

EventBus (任意组件传值)

EventBus (事件总线)是一个独立的事件中心,用于管理不同组件间的传值操作。只负责传值, 本身不存储数据.

EventBus 通过一个新的 Vue 实例来管理组件传值操作,组件通过给实例注册事件、调用事件来实现数据传递。通常被存储在一个单独的文件中.

image.png

优点:

  1. data 中不会存在许多与当前组件功能无关的数据。
  2. 不需要寻找组件之间的关系.

做法:

发送数据的组件触发 bus 事件,接收的组件给 bus 注册对应事件。

  1. 发送数据的组件触发 bus 事件

image.png

  • 之前整理用的是this.$emit(), 指的是当前组件的vue实例. 这里使用bus.$emit()是用的bus组件的vue实例, 而bus可以被任意组件访问.
  1. 接收的事件给 bus 注册对应事件通过 $on() 操作。

image.png

  • created()是生命周期函数, 因为要使用totalCount, 所以要在实例创建完毕后进行处理.

  • 参数一: 事件名称

  • 参数二: 事件处理程序. 箭头函数中的this指的是当前这个product-total组件.因为箭头函数不会修改this, 因为它本身没有this, 它所用的this是当前环境(creat())内部的this, 也就是当前函数的实例. 如果这里使用普通函数的话, 需要先在外部做一个存储.

<body>
  <div id="app">
    <h3>购物车</h3>
    <product-item v-for="product in products" :key="product.id" :title="product.title"></product-item>
    <product-total></product-total>
  </div>
  <script src="lib/vue.js"></script>
  <script src="EventBus.js"></script>
  <script>
    Vue.component("productItem",{
      props: ['title'],
      template: `
      <div>
        <span>商品名称: {{ title }}, 商品个数: {{count}}</span>
        <button @click="onChange">+1</button>
      </div>
      `,
      data: function(){
        return {
          count: 0
        }
      },
      methods: {
        onChange(){
          // 给bus触发自定义事件,传递数据
          bus.$emit("countChange",1);
          this.count++;
        }
      }
    })

    Vue.component("productTotal",{
      template:`
      <span>总个数为: {{totalCount}}</span>
      `,
      data: function(){
        return {
          totalCount: 0
        }
      },
      created(){
        // 给 bus 注册事件,并接收数据
        bus.$on("countChange", (productCount) => {
          this.totalCount += productCount;
        })
      }
    })

    var bus = new Vue();

    // 根实例
    new Vue({
      el: '#app',
      data: {
        products: [
          {
            id: 1,
            title:"苹果"
          },
          {
            id:2,
            title:"香蕉"
          },
          {
            id: 3,
            title:"橙子"
          }
        ]
      }
    });
  </script>
</body>
复制代码

image.png

其他传值方式

如果没有特殊需求, 不建议使用. 出错时比较难排除错误.

$root

$root 用于访问当前组件树根实例,设置简单的 Vue 应用时可以通过此方式进行组件传值。

除了 $root , Vue.js 中还提供了 $parent$children 用于便捷访问父子组件。

  • A和B组件都是直接访问vue根实例中的count.
<body>
  <div id="app">
    <com-a></com-a>
  </div>
  <script src="lib/vue.js"></script>
  <script>
    // 根实例的子组件A的子组件B
    var ComB = {
      template: `
        <div>
          组件B: {{ $root.count }}
          <button @click="clickFn">按钮</button>
        </div>
      `,
      methods: {
        clickFn () {
          this.$root.count = 200;
        }
      }
    };

    // 子组件A
    var ComA = {
      template: `
        <div>
          组件A: {{ $root.count }}
          <button @click="clickFn">按钮</button>
          <com-b></com-b>
        </div>
      `,
      methods: {
        clickFn () {
          this.$root.count = 100;
        }
      },
      components: {
        ComB
      }
    };


    // 根实例
    new Vue({
      el: '#app',
      data: {
        count: 0
      },
      components: {
        ComA
      }
    });
  </script>
</body>
复制代码

image.png

点击A按钮, count都变为100; 点击B按钮, count都变为200.

$refs

$refs 用于获取设置了 ref 属性的 HTML 标签子组件

给普通 HTML 标签设置 ref 属性,$refs 可以获取 DOM 对象。

  • 通过this.$refs.inp可以获取到<input>标签, 给它设置focus()可以使<input>自动获取焦点.

image.png

image.png

组件插槽

后续补充

内置组件

后续补充

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享