乐趣区

第十一课时-关于Vue组件一些注意的地方

组件的基本使用

注册组件

1. 注册组件就是利用 Vue.component()方法
2. 可以理解为一个 vue 文件就是一个组件

模板要求

组件的模板中只能有一个根元素

组件中的 data 必须是函数

var app = new Vue({data () {
      return {message: 'hello world'}
    }
})

组件的属性和事件

例,一个父组件:

<my-component v-bind:foo="baz" v-on:event-a="doThis(arg1,...arg2)"></my-component>

上面代码

  • foo<my-component> 组件内部定义的一个 prop 属性,baz是父组件的一个 data 属性,
  • event-a是子组件定义的一个事件,doThis是父组件的一个方法

执行过程应该是这样的:

  • 父组件把 baz 数据通过 prop 传递给子组件的foo
  • 子组件内部得到 foo 的值,就可以进行相应的操作;
  • 当子组件内部发生了一些变化,希望父组件能知道时,就利用代码触发 event-a 事件,把一些数据发送出去
  • 父组件把这个事件处理器绑定为 doThis 方法,子组件发送的数据,就作为 doThis 方法的参数被传进来
  • 然后父组件就可以根据这些数据,进行相应的操作

属性 Props

Vue 组件通过 props 属性来声明一个自己的属性,然后父组件就可以往里面传递数据。

Vue.component('mycomponent',{template: '<div> 这是一个自定义组件, 父组件传给我的内容是:{{myMessage}}</div>',
    props: ['myMessage'],
    data () {
      return {message: 'hello world'}
    }
  })

然后调用该组件

<div id="app">
    <mycomponent my-message="hello"></mycomponent>
</div>

注意,由于 HTML 特性是不区分大小写的,所以传递属性值时,myMessage应该转换成 kebab-case (短横线隔开式)my-message="hello"

v-bind 绑定属性值

<div attr="message">hello</div>

上面这样,div元素的 attr 特性值就是message。而这样

<div v-bind:attr="message">hello</div>

这里的 message 应该是 Vue 实例的 data 的一个属性,这样 div 元素的 attr 特性值就是 message 这个属性的值。
之所以说是一般情况,是因为 classstyle特性并不是这样。用 v-bind:classclass传入正常的类名,效果是一样的,因为对于这两个特性,Vue 采用了合并而不是替换的原则。

子组件希望对传入的 prop 进行操作

根据上面,想要把父组件的属性绑定到子组件,应该使用 v-bind,这样,父组件中数据改变时能反映到子组件。
注意 在子组件中最好不要更改父组件传入的值,如果非要更改的话,加上.sync 修饰符

例父组件

<my-component :child-array.sync="parentArray"></my-component>

子组件

methods: {changeArray () {
       this.counter++
       this.$emit('update:childArray', this.counter)
     }
}

给子组件传递正确类型的值

同样是上面的原因,静态的给子组件的特性传递值,它都会把他当做一个字符串。

<!-- 传递了一个字符串 "1" -->
<comp some-prop="1"></comp>

子组件中,特性的值是字符串 “1” 而不是 number 1。如果想传递正确的数值,应该使用 v -bind 传递,这样就能把传递的值当做一个表达式来处理,而不是字符串。

<!-- 传递实际的 number 1 -->
<comp v-bind:some-prop="1"></comp>

Prop 验证

我们可以给组件的 props 属性添加验证,当传入的数据不符合要求时,Vue 会发出警告。

Vue.component('example', {
  props: {// 基础类型检测 (`null` 意思是任何类型都可以)
    propA: Number,
    // 多种类型
    propB: [String, Number],
    // 必传且是字符串
    propC: {
      type: String,
      required: true
    },
    // 数字,有默认值
    propD: {
      type: Number,
      default: 100
    },
    // 数组 / 对象的默认值应当由一个工厂函数返回
    propE: {
      type: Object,
      default: function () {return { message: 'hello'}
      }
    },
    // 自定义验证函数
    propF: {validator: function (value) {return value > 10}
    }
  }
})

type 可以是下面原生构造器:

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array
  • Symbol

type 也可以是一个自定义构造器函数

自定义事件

通过 prop 属性,父组件可以向子组件传递数据,而自定义事件就是用来将子组件的内部数据报告给父组件的。

<div id="app3">
    <my-component2 v-on:myclick="onClick"></my-component2>
</div>
<script>
  Vue.component('my-component2', {
    template: `<div>
    <button type="button" @click="childClick"> 点击我触发自定义事件 </button>
    </div>`,
    methods: {childClick () {this.$emit('myclick', '这是我暴露出去的数据', '这是我暴露出去的数据 2')
      }
    }
  })
  new Vue({
    el: '#app3',
    methods: {onClick () {console.log(arguments)
      }
    }
  })
</script>

如上所示,共分为以下步骤:

  1. 子组件在自己的方法中将自定义事件以及需要发出的数据通过以下代码发送出去

    this.$emit('myclick', '这是我暴露出去的数据', '这是我暴露出去的数据 2')
    第一个参数是自定义事件的名字
    后面的参数是依次想要发送出去的数据
    
  2. 父组件利用 v-on 为事件绑定处理器

    <my-component2 v-on:myclick="onClick"></my-component2>

    这样, 在 Vue 实例的 methods 方法中就可以调用传进来的参数了

注意 :在使用v-on 绑定事件处理方法时,不应该传进任何参数,而是直接写v-on:myclick="onClick", 不然,子组件暴露出来的数据就无法获取到了

绑定原生事件

如果想在某个组件的根元素上监听一个原生事件。可以使用 .native 修饰 v-on

探究v-model

v-model可以对表单控件实现数据的双向绑定,它的原理就是利用了绑定属性和事件来实现的。比如 input 控件。不使用v-model,可以这样实现数据的双向绑定:

<div id="app4">
    <input type="text" v-bind:value="text" v-on:input="changeValue($event.target.value)">
    {{text}}
</div>
<script>
  new Vue({
    el: '#app4',
    data: {text: '444'},
    methods: {changeValue (value) {this.text = value}
    }
  })
</script>

上面的代码同样实现了数据的双向绑定。其本质就是:

  • inputvalue特性绑定到 Vue 实例的属性 text 上,text改变,input中的内容也会改变
  • 然后把表单的 input 事件处理函数设置为 Vue 实例的一个方法,这个方法会根据输入参数改变 Vue 中 text 的值
  • 相应的,在 input 中输入内容时,触发了 input 事件,把 event.target.value 传给这个方法,最后就实现了改变绑定的数据的效果。

v-model 就是上面这种方式的语法糖,也就是把上面的写法封装了一下,方便我们使用。

使用 slot 分发内容

单个 slot

上面用到的很多组件的使用方式是这样的:

<component></component>

也就是说组件中是空的,没有放置任何文本或元素。但是原生的 html 元素都是可以进行嵌套的,div里面放 table 什么的。自定义组件开闭标签之间也可以放置内容,不过需要在定义组件时使用slot

slot相当于子组件设置了一个地方,如果在调用它的时候,往它的开闭标签之间放了东西,那么它就把这些东西放到 slot 中。

  1. 当子组件中没有 slot 时,父组件放在子组件标签内的东西将被丢弃;
  2. 子组件的 slot 标签内可以放置内容,当父组件没有放置内容在子组件标签内时,slot 中的内容会渲染出来;
  3. 当父组件在子组件标签内放置了内容时,slot 中的内容被丢弃

子组件的模板:

<div>
  <h2> 我是子组件的标题 </h2>
  <slot>
    只有在没有要分发的内容时才会显示。</slot>
</div>

父组件模板:

<div>
  <h1> 我是父组件的标题 </h1>
  <my-component>
    <p> 这是一些初始内容 </p>
  </my-component>
</div>

渲染结果:

<div>
  <h1> 我是父组件的标题 </h1>
  <div>
    <h2> 我是子组件的标题 </h2>
    <p> 这是一些初始内容 </p>
  </div>
</div>

具名 slot

slot可以有很多个。那么子组件对于父组件放置的多余的内容如何放到各个 slot 中呢?方法就是子组件给每个 slot 起一个名字 name,父组件放置多余的元素时,给每个元素的slot 属性分配一个代表 slot 的名字。到时候,多余的内容就会根据自己的 slot 属性去找具有对应名字的 slot 元素。

注意:

  1. 子组件可以有一个匿名的 slot,当分发的多余内容找不到对应的 slot 时,就会进入这里面
  2. 如果子组件没有匿名的 slot,当分发的多余内容找不到对应的 slot 时,就会被丢弃

例如,假定我们有一个 app-layout 组件,它的模板为:

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

父组件模版:

<app-layout>
  <h1 slot="header"> 这里可能是一个页面标题 </h1>
  <p> 主要内容的一个段落。</p>
  <p> 另一个主要段落。</p>
  <p slot="footer"> 这里有一些联系信息 </p>
</app-layout>

渲染结果为:

<div class="container">
  <header>
    <h1> 这里可能是一个页面标题 </h1>
  </header>
  <main>
    <p> 主要内容的一个段落。</p>
    <p> 另一个主要段落。</p>
  </main>
  <footer>
    <p> 这里有一些联系信息 </p>
  </footer>
</div>

作用域插槽

作用域插槽也是一个插槽slot,但是他可以把数据传递给到父组件的特定元素内,然后有父组件决定如何渲染这些数据。

  1. 首先,子组件的 slot 需要有一些特性(prop)

    Vue.component('my-component4', {
         template: `<div>
           <slot :text="hello" message="world"></slot>
         </div>`,
         data () {
           return {hello: [1,'2']
           }
         }
       })
  2. 父组件在调用子组件时,需要在里面添加一个 template 元素,并且这个 template 元素具有 scope 特性

    <div id="app7">
      <my-component4>
        <template scope="props">
        </template>
      </my-component4>
     </div>

    scope特性的值,就代表了所有子组件传过来的数据组成的对象。相当于

    props = {
        text: '',
       message: ''
    }
  3. 最后,父组件就可以在 template 中渲染子组件传过来的数据了

    <div id="app7">
       <my-component4>
         <template slot-scope="props">
           <span>{{props.text}}</span>
           <span>{{props.message}}</span>
         </template>
       </my-component4>
      </div>

    作用域插槽也是插槽,只不过是多加了些特性,然后父组件多进行了些处理。

退出移动版