Vue.js笔记7—组件

183 阅读5分钟

组件

  • 组件是可复用的 Vue 实例,且带有一个名字

  • 每用一次组件,就会有一个它的新实例被创建

  • data 必须是一个函数

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

1. 注册组件

  • 全局注册

    可以用在任何新创建的 Vue 根实例的模板中。第一个参数是组件名

    Vue.component('my-component-name', {
      // ... options ...
    })
    
  • 局部注册

    即在哪注册在哪用,这样就不会过多的占用内存,首先注册一个局部组件

    var ComponentA={
        props:['title'],
        template:`<h3>{{title}}</h3>`
    }
    

    然后实例的components对象中使用,属性名就是组件名,属性值就是你想传入的组件

    new Vue({
      el: '#app',
      components:{
          'blog-title':ComponentA
      }
    })
    

注意:推荐使用kebab-case(短横线分割命名)给组件命名,例如该例子中的<blog-title>,否则容易报错:

image.png

注意:每个组件必须只有一个根元素,即template里只能有一个根标签

2. Prop

#prop大小写

prop对大小写不敏感,所以可以这样使用:

Vue.component('blog', {
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})
<blog post-title="hello!"></blog>

#prop类型

一般情况下是以字符串数组形式列出的 prop:

props: ['title', 'likes', 'isPublished', 'commentIds', 'author']

也可以给每个prop指定值类型。属性名是prop名称,属性值是prop类型

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}
//也可以是Data、Symbol

#传递prop

一个例子

Vue.component('blog-post', {
  props: ['title'],	//给组件定义一个属性
  template: '<h3>{{ title }}</h3>'
})
<blog-post title="My journey with Vue"></blog-post> //传值
  • 静态传递

    # 传入字符串
    <blog-post title="My journey with Vue"></blog-post>
    
    #有时候需要用v-bind告诉vue这是一个js表达式而不是字符串
    # 传入布尔值,
    <blog-post :is-published="false"></blog-post>
    # 传入数组
    <blog-post :comment-ids="[234, 266, 273]"></blog-post>
    # 传入对象
    <blog-post :author="{name: 'Veronica',company: 'Veridian Dynamics'}"></blog-post>
    # 传入一个对象所有的属性
    
  • 动态传递

    new Vue({
      el: '#blog',
      data: {
        posts: [
          { id: 1, title: 'My journey with Vue' },
          { id: 2, title: 'Blogging with Vue' },
          { id: 3, title: 'Why Vue is so fun' }
        ]
      }
    })
    
    <blog-post v-for="post in posts" :key="post.id" :title="post.title" ></blog-post>
    

    当需要传递的属性很多时,可以将这些属性成合成一个。在这个例子中,可以把所有post所有的属性如title、content写在一个post 中:

  1. 重构组件

    原本子组件

    Vue.component('blog-post', {
      props: ['title','content'],
      template: `
        <div>
          <h3>{{ title }}</h3>
          <div v-html="content"></div>
        </div>
      `
    })
    

    现在子组件

    Vue.component('blog-post', {
      props: ['post'],
      template: `
        <div>
          <h3>{{ post.title }}</h3>
          <div v-html="post.content"></div>
        </div>
      `
    })
    
  2. 创建实例

    new Vue({
      el: '#blog',
      data: {
        posts: [
          { id: 1, title: 'My journey with Vue',content:'111' },
          { id: 2, title: 'Blogging with Vue',content:'222' },
          { id: 3, title: 'Why Vue is so fun',content:'333'}
        ]
      }
    })
    
  3. 在实例中使用组件

    原本父级

    <blog-post v-for="post in posts" :key="post.id" :title="post.title" :content="post.comtent"></blog-post>
    

    现在父级

    <blog-post v-for="post in posts" :key="post.id" :post="post"></blog-post>
    

#单项数据流

父级 prop 的更新会向下流动到子组件中,但是反过来则不行

#prop验证

可以为组件的 prop 指定验证要求。

  • 当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
  • prop 会在一个组件实例创建之前进行验证
Vue.component('my-component', {
  props: {
    //1. 基础的类型检查 (null和undefined会通过任何类型验证)  
    propA: Number,
      
    //2. 多个可能的类型
    propB: [String, Number],
      
    //3. 必填的字符串
    propC: {				 
      type: String,
      required: true
    },
      
    //4. 带有默认值的数字
    propD: {				 
      type: Number,
      default: 100
    },
      
    //5.自定义验证函数。下面例子表示必须匹配下列字符串中的一个
    propF: {				  
      validator: function (value) {
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})

3. 自定义事件

#事件名

推荐始终使用 kebab-case 的事件名

this.$emit('my-event')
<my-component @my-event="doSomething"></my-component>

#监听子组件事件

  1. 创建实例

    new Vue({
      el: '#demo',
      data: {
        posts: [/* ... */],
        postFontSize: 1
      }
    })
    
  2. $emit给子组件添加事件

    Vue.component('blog-post', {
      props: ['post'],
      template: `
        //...
          <button @click="$emit('enlarge-text')">Enlarge</button>
         //...
      `
    })
    
  3. v-on监听子组件事件。当子组件触发时,开始响应

    <div id="demo">
      <div :style="{ fontSize: postFontSize + 'em' }">
        <blog-post 
          ...
          @enlarge-text="postFontSize += 0.1"
         ></blog-post>
     </div>
    </div>
    

$emit可以接收第二个参数

  1. 给子组件事件添加参数

    <button v-on:click="$emit('enlarge-text', 0.1)">Enlarge</button>
    
  2. 父级通过$event获取整个参数值

    <blog-post
      ...
      @enlarge-text="postFontSize += $event"
    ></blog-post>
    

    第二种写法:

    <blog-post
    ...
    @enlarge-text="enlargeText"
    ></blog-post>
    

    传过来的参数值会作为该方法的第一个参数

    methods: {
       enlargeText: function (num) {
         this.postFontSize += num
       }
    }
    

#自定义事件中的v-model

  • 普通情况下自定义事件

    <input v-model="searchText">
    

    等价于

    <input :value="searchText" @input="searchText = $event.target.value">
    
  • 在组件上自定义事件

    <custom-input v-model="searchText"/>
    

    等价于

    <custom-input :value="searchText" @input="searchText = $event"/>
    

    这时候需要更改子组件,才能让其正常运作。

    Vue.component('custom-input', {
      props: ['value'],
      template: `
        <input
          :value="value"
          @input="$emit('input', $event.target.value)"
        >
      `
    })
    

    解析:这时子组件的input触发时,会抛出$event.target.value,父级会监听到并接收$event.target.value。此时父级的$event实际上就等于子组件抛出的$event.target.value,变化后的searchText的值会绑定在父级的value上,然后通过props传回子组件,子组件接收到这个props值后就绑定在input的value上,这时候就在页面呈现出效果了

#绑定原生事件

使用 v-on.native 修饰符:

<base-input @focus.native="onFocus"></base-input>

注意.native监听的是组件的根元素。如果想监听到子元素,需要用到$listeners对象

4. 插槽

#一个例子

  1. 在子组件模板中加上一个插槽<slot></slot>

    Vue.component('alert-box', {
      template: `
        <div>
          <strong>Error!</strong>
          <slot></slot>
        </div>
      `
    })
    
  2. 在父级组件内添加内容后,内容就会自动放进插槽内

    <alert-box>Something bad happened.</alert-box>
    

注意:如果子的 template 中没有包含一个 slot 元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃

#默认内容

默认内容会在没有提供内容的时候被渲染。只需要将内容放在slot中:

//在一个子组件的template中
<button>
  <slot>Submit</slot>
</button>
//1. 父级中没有添加任何内容
<submit-button></submit-button>
//   渲染后备内容:
<button>Submit</button>
//2. 父级中有内容
<submit-button>Save</submit-button>
//   后备内容会被取代:
<button>Save</button>

#具名插槽

当我们需要多个插槽时,具名插槽很有用:

  1. 组件模板中,给slot添加name属性

    //假设这是base-layout组件的模板,需要分别给header、main、footer添加插槽:
    <div class="container">
      <header>
        <slot name="header"></slot>
      </header>
      <main>
        <slot></slot> //这个插槽没有名字,name的值默认为default
      </main>
      <footer>
        <slot name="footer"></slot>
      </footer>
    </div>
    
  2. 父级中,用带有v-slottemplate包裹内容,v-slot指定插槽名称

    <base-layout>
      <template v-slot:header>
        <h1>I am a header</h1>
      </template>
    
      //任何没有按规定包裹的内容都会被视为默认插槽的内容
      <p>A paragraph for the main content.</p>
      <p>And another one.</p>
    
      <template v-slot:footer>
        <p>I am a footer</p>
      </template>
    </base-layout>
    

    v-slot可以缩写:

    <template #header>
       <h1>I am a header</h1>
    </template>
    

#编译作用域

插槽的作用域与实例相同。即能访问到实例的数据,而不能访问组件的数据

//可以访问
<navigation-link url="/profile">{{ user.name }}</navigation-link>
//不可以访问
<navigation-link url="/profile">{{ url }}</navigation-link>

#插槽作用域

如果想在父级插槽内容中访问到子组件的数据

  1. 在子组件的slot中绑定数据

    Vue.component('user-info', {
        //user作为属性绑定数据。在这里user属性被称为插槽prop
        template: `<span><slot :user="userdata">{{userdata.current}}</slot></span>`,
        data:()=>({
            userdata:{
                before:'lihua',
                current:'xiaoming'
            }
        }),
    })
    
  2. v-slot 来定义插槽 prop 的名字。名字也可以是其他

    <user-info v-slot:default="slotProps">
        {{slotProps.user.before}}
    </user-info>
    

    由于指定的插槽是default,所以以上也可以简写成:注意不要与具名插槽混淆

    <user-info v-slot="slotProps">
        {{slotProps.user.before}}
    </user-info>
    

自 2.6.0 起有所更新。已废弃的使用 slot-scope

# 动态插槽名

<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>
</base-layout>

5. 动态组件

如果在一个多标签界面实现组件切换,可以通过 Vue 的 <component> 元素加一个特殊的 is attribute 来实现::

<!-- 切换tab值 -->
<button v-for="tab in tabs" :key="tab" @click="currentTab = tab">{{tab}}</button>
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>
Vue.component("com1", {
    template: "<div>com1</div>"
});
Vue.component("com2", {
    template: "<div>com2</div>"
});
Vue.component("com3", {
    template: "<div>com3</div>"
});

new Vue({
  el: '#app',
  data:{
    currentTab:"com1",
    tabs:["com1", "com2", "com3"]
  },
    computed: {
		//currentTabComponent可以是已注册组件的名字,或一个组件的选项对象        
        currentTabComponent: function () {
            return this.currentTab
        }
    }
})