Vue组件使用及组件传值方式

460 阅读6分钟

组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 datacomputedwatchmethods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。

定义组件的名称写法

使用 kebab-case

Vue.component('my-component', { /* ... */ })

使用自定义组件 <my-component>

使用 PascalCase

Vue.component('MyComponent', { /* ... */ })

使用自定义组件 <MyComponent>

全局注册

这种方式是全局注册组件

Vue.component('MyComponent', { /* ... */ })
new Vue({ el: '#app' })

局部注册

这是局部注册组件,局部注册组件的好处主要是避免一次性加载多个组件,只加载所需的组件

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

es6写法

import ComponentA from './ComponentA.vue'

export default {
  components: {
    ComponentA
  },
  // ...
}

组件data必须是一个函数

每个组件的实例生成的data独立拷贝,避免操作某一个组件里的data里的属性影响其他相同的组件值

props

在子组件里添加props选项,props是一个数组,里面的每一项都是接受父组件传递过来的值

const buttonComponent = {
    name: 'buttonComponent',
    props: ['postTitle'],
    data() {
        return {
            count: 0
        }
    },
    template: '<div v-bind:title="postTitle" v-on:click="count++">点击我变化{{count}}</div>'
};

父组件如果在teplate里调用的话,属性名称可以通过驼峰式大小写命名来传递

var app = new Vue({
    el: '#app',
    name: 'app',
    components: {
        'button-counte': buttonComponent
    },
    template:'<button-counte postTitle="123123"> </button-counte>'
});

如果组件是在html代码里去调用只能使用kebab-case方式,因为HTML对大小写不敏感

var app = new Vue({
    el: '#app',
    name: 'app',
    components: {
        'button-counte': buttonComponent
    }
});
<body>
    <div id="app">
        <button-counte post-title="11112"> </button-counte>
    </div>
</body>

prop值类型

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}

prop传递静态和动态值

静态之传入的写法

<button-counte postTitle="123123"> </button-counte>

动态之传入的写法,通过v-bind:去绑定动态值

const value ='1111';
<button-counte v-bind:postTitle="value"> </button-counte>

传递数字(number),依然需要用到v-bind:

<button-counte v-bind:postTitle="42"> </button-counte>

传入一个布尔值

<!-- 当postTitle值等于 false -->
<button-counte v-bind:postTitle="false"> </button-counte>
<!-- 当postTitle值等于 true -->
<button-counte postTitle> </button-counte>

传入一个数组和传入一个对象

<!--尽管数组和对象都是静态传入,但是都是都需要v-bind  静态数组-->
<button-counte v-bind:comment-ids="[234, 266, 273]"></button-counte>
<!--静态对象-->
<button-counte 
  v-bind:author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
></button-counte >

传入一个对象的所有 property 传入整个对象属性,v-bind (取代 v-bind:prop-name)。例如,对于一个给定的对象 post

post: {
  id: 1,
  title: 'My Journey with Vue'
}

<blog-post v-bind="post"></blog-post>

单向数据流

prop向子组件传值是一个单向从上到下的一个方式,反过来却不行,直接在子组件里改变prop传过来的值浏览器将会报错。
我们可以通过data的属性去接受

props: ['initialCounter'],
data: function () {
  return {
    counter: this.initialCounter
  }
}

prop类型检查和验证

prop type类型可以是以下类型

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol 除此之外,还可以自定义类型,需要自定义构造函数,并且通过instanceof来检查确认
function Person (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}
Vue.component('blog-post', {
  props: {
    author: Person
  }
})

验证author属性是否由Person创建

如果没有定义prop接受传递的参数,默认会把prop传过来的属性添加到组件的dom属性中,例:

我们给button-counte绑定abc传给prop

<button-counte postTitle="123123" v-bind:abc="message"> </button-counte>

但是我们在子组件中并没有定义prop去接受

const buttonComponent = {
    name: 'buttonComponent',
    props: ['postTitle'],
    data() {
        return {
            count: 0
        }
    },
    template: '<div v-bind:title="postTitle" v-on:click="count++">点击我变化{{count}}</div>'
};

浏览器dom结构如下

<div title="123123" abc="hello">点击我变化0</div>

class 和 style自动合并

如果在父组件传入classstyle

<button-counte  class="f" style="font-size:16px;"> </button-counte>

子组件有默认的classstyle

const buttonComponent = {
    name: 'buttonComponent',
    template: '<div  class="c" style="color:red">点击我变</div>'
}

浏览器dom结构如下

<div class="c f" style="color: red; font-size: 16px;">点击我变</div>

除此之外,父组件向子组件传递其他属性,将会优先于子组件定义的属性,父组件将会覆盖替换子组件的属性

属性继承

如果我们给子组件加入value属性,默认子组件会继承整个属性

<button-counte value="123"  class="f" style="font-size:16px;"> </button-counte>

浏览器dom

<div class="c f" value="123" style="color: red; font-size: 16px;">点击我变</div>

如果我们要取消属性继承,可以在子组件里添加inheritAttrs: false选项

const buttonComponent = {
    name: 'buttonComponent',
    inheritAttrs: false,
    template: '<div  class="c" style="color:red">点击我变</div>'
};
var app = new Vue({
    el: '#app',
    name: 'app',
    components: {
        'button-counte': buttonComponent
    },
    data: {
        message: 'hello'
    },
    template:'<button-counte value="123"  class="f" style="font-size:16px;"> </button-counte>'
});

浏览器dom将不会再继承value这个属性

<div class="c f" style="color: red; font-size: 16px;">点击我变</div>

当然这个对classstyle不起作用

自定义事件

父组件监听子组件的事件

父组件通过在调用子组件时,在组件属性上添加v-on来监听子组件的事件

<button-counte v-bind:message="message" v-on:hand="change"> </button-counte>

子组件通过$emit方法来触发父组件的hand事件,从而调用父组件的change方法:

完整代码

const buttonComponent = {
    name: 'buttonComponent',
    template: `<div v-on:click="$emit('hand')">{{message}}</div>`,
    props:['message']
};
var app = new Vue({
    el: '#app',
    name: 'app',
    components: {
        'button-counte': buttonComponent
    },
    data: {
        message: 'hello'
    },
    methods:{
        change(){
            this.message = this.message+ 'hellow';
        }
    },
    template:'<button-counte v-bind:message="message" v-on:hand="change"> </button-counte>'
});

$emit第一个参数传事件名称,第二个参数可以传递参数,在父组件里的事件下的方法可以调用第二个参数

//子组件
<div v-on:click="$emit('hand','123')">{{message}}</div>
//父组件
methods:{
  change(e){
      this.message = this.message+ 'hellow';
      console.log(e) //输出'123'
  }
},
template:'<button-counte v-bind:message="message" v-on:hand="change"> </button-counte>'

面试中常问到的v-model

v-model实际上是一种语法糖,真正实现的原理如下:

<!--v0-model-->
<input v-model="data">

input v-model等价于下面代码,实际上为我们做了两步,一个是绑定input的value值,一个是监听input值的变化

<input
  v-bind:value="data"
  v-on:input="data = $event.target.value"
>

这是v-model实现的双向绑定的原理

slot插槽

基本用法

子组件定义插槽位置

const buttonComponent = {
    name: 'buttonComponent',
    template: `<div><slot></slot></div>`,
};

父组件调用组件,在组件中两个标签中间添加对应的内容,将会传递给子组件插槽的位置

var app = new Vue({
    el: '#app',
    name: 'app',
    components: {
        'button-counte': buttonComponent
    },
    template:'<button-counte>这是一个插槽 </button-counte>'
});

浏览器dom

<div>这是一个插槽 </div>

当然父组件还可以传入html代码

<button-counte><span style="color:red">这是一个插槽 </span></button-counte>

浏览器dom

<div><span style="color: red;">这是一个插槽 </span></div>

这就是slot的魅力

slot默认值和默认值替换

如果在子组件里<slot>abc</slot>标签中添加其他内容(例如:abc),可以理解为是插槽的默认值,父组件如果直接调用就会显示abc

<button-counte></button-counte>
<!--以上会显示`abc`-->

如果父组件调用改组件时候往插槽里添加内容,那么子组件里的abc将会被父组件传来的内容替换

<button-counte>hello word</button-counte>
<!--以上会显示`hello word`-->

slot具名插槽

具名插槽就是给slot定义名字,通过在<slot></slot>中添加 name="header"属性,在父组件中,通过<template v-slot:header>来调用,下面我们以网页布局为一个例子:

const buttonComponent = {
    name: 'buttonComponent',
    template: 
    `<div class="container">
        <header>
            <slot name="header"></slot>
        </header>
        <main>
            <slot></slot>
        </main>
        <footer>
            <slot name="footer"></slot>
        </footer>
    </div>`,
};
var app = new Vue({
    el: '#app',
    name: 'app',
    components: {
        'button-counte': buttonComponent
    },
    template:
        `<button-counte>
            <template v-slot:header>
                <h1>这是header</h1>
            </template>
            <template v-slot:footer>
                <h1>这是footer</h1>
            </template>
            <div>这是中间内容</div>
        </button-counte>`
});

打开浏览器一看dom如下

<div class="container">
  <header><h1>这是header</h1></header> 
  <main>  <div>这是中间内容</div></main> 
  <footer><h1>这是footer</h1></footer>
</div>

我们发现headerfooter插槽能够找到自己的位置,而中间的没有添加名字的也能将默认值添加进去,具名插槽不受顺序影响

作用域插槽

父组件如果要访问子组件的变量值可以通过插槽方式去使用,子组件首先需要通过v-binduser数据绑定在slot上,父组件通过v-slot加具名来获取变量,完整代码如下

const buttonComponent = {
    name: 'buttonComponent',
    data(){
        return{
            user: {
                first: 'li',
                last: 'jie'
            }
        }
    },
    template: 
    `<div class="container">
        <slot v-bind:user="user">{{user.last}}</slot>
    </div>`,
};
var app = new Vue({
    el: '#app',
    name: 'app',
    components: {
        'button-counte': buttonComponent
    },
    template:
        `<button-counte>
            <template v-slot:default="slotProps">
                {{ slotProps.user.first }}{{slotProps.user.last}}
            </template>
        </button-counte>`
});

浏览器dom

<div class="container">
    lijie
</div>

但是,我们不能在<template>使用{{slotProps.user.last}},否则会报错

<div>
    <button-counte>
        <template v-slot:default="slotProps">
            {{ slotProps.user.first }}{{slotProps.user.last}}
        </template>
    </button-counte>
    {{slotProps.user.last}}
</div>

具名插槽缩写

例如 v-slot:header 可以被重写为 #header

<button-counte>
      <template #heade>
          ....
      </template>
</button-counte>

动态组件和异步组件

keep-alive可以缓存动态组件

<!--抄的官网例子-->
<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>

异步组件 下回再接着研究。