前言
在Vue
中,万物皆组件,学习组件乃是Vue
的根基所在,假如一个页面相当于一张图,那么组件就是一个小小的拼图,通过组件可以拼出各式各样的图,这也是Vue
的魅力所在。
正文
基本示例
Vue.component('button-counter',{
data() {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
上面我们创建了一个全局的名字为button-couter
的组件,当我们要在根实例(app.vue
)中使用它的时候,直接把这个组件作为自定义的标签来使用。
<div>
<button-counter></button-counter>
</div>
局部注册
上面演示了如何全局注册一个组件,但是组件不能全部都全局注册,会造成代码的冗余,所以大部分的组件需要局部注册。
如何局部注册呢,首先把一个组件作为一个单个的文件放在components
文件夹下,在文件中将组件export
,然后在实例中import
它就实现了组件的局部注册。
// 组件文件的基本格式
// components/buttonCounter.vue
<template>
// 模板内容
</template>
<script>
export default {
name: '', //这个name对外没有实际作用,但是可以让该组件递归调用自己
data() {
return {}
}
}
</script>
<style scoped>
// scoped标明style里面样式仅适用于当前组件
</style>
下面我们在实例中调用它
import buttonCounter from './component/buttonCounter'
// 引入组件,组件的名字也可以随意定义(假如定一个abc),下面使用它的时候就用<abc></abc>
<buttonCounter></buttonCounter>
基础组件的自动全局化注册
有一些组件特别小,但是复用率特别高,必须输入框按钮之类的,这些组件就必须全局注册,但是组件又特别多,一个个注册显得代码特别臃肿,所以下面就用一段代码来实现自动注册。
假如我们把基础组件放在components
文件夹下,组件的文件名都以Base开头
,比如BaseInput
,BaseButton
。首先我们引入符合Base
开头匹配规则的所有组件,这里要用到require.context
方法,然后用文件的名字来注册全局组件。
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context(
'./components', //根目录
false, // 是否查询子目录
/Base[A-Z]\w+\.(vue|js)$/ //匹配规则
)
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName)
const componentName = upperFirst(
camelCase(
fileName.replace(/^\.\/(.*)\.\w+$/, '$1')
)
)
Vue.component(
componentName,
componentConfig.default || componentConfig
// 组件中 export default 和 export
)
})
上面代码中upperFirst
和camelCase
都是lodash库里面的方法。
组件里面也是有
computed
,methods
,watch
和生命周期钩子
的,唯独没有el
,这是根实例独有的
那么data
为何是个函数而不是对象呢,这是因为组件是可以复用的,每次使用就创建一个新的实例,把data
里面的属性作为函数的返回值的话,它就实现了每个实例的属性的私有化,跟闭包一样原理。
通过prop
向组件传递数据
Vue.component('blog-post',{
props: ['title','name'],
template: '<h3>{{ name }}{{ title }}</h3>'
})
我们创建了一个blog-post
组件,并用prop
接收了2个参数title
和name
,下面在根实例中往组件中注入值
<blog-post title="myTitlt" name="myName"></blog-post>
上面是传了几个单个值,假如传的值比较多,或者传的值是个数组的时候就要换一个方式
Vue.component('blog-post',{
props: ['post'],
template: '<h3>{{ post.name }}{{ post.title }}</h3>'
})
根实例中传值
<blog-post v-for="post in posts" :post="post"></blog-post>
当父级传给子组件值时候,子组件改变这个值也会同时更改父级的值,这样就会发生混乱,所以当子组件有改变值的情况下,最好将父级传来的值赋给自己的属性
props: ['name'],
data() {
return {
myName: this.name
}
}
组件向父级传值
这里要用到一个Vue
的内置方法$emit
,这个函数就相当于js
中的trigger()
,就是在组件中可以触发实例中绑定的事件,而且这个方法可以传递参数。
Vue.component('welcome-button', {
template: `
<button v-on:click="$emit('welcome',1)">
Click me to be welcomed
</button>
`
})
组件中触发welcome
事件并传了个参数1,那么在实例中绑定这个事件
<welcome-button @welcome="couter=$event"></welcome-button>
<welcome-button @welcome="sayHi"></welcome-button>
new Vue({
data: {
couter: 0
},
methods: {
sayHi(argsCouter) {
// ..
}
}
})
在实例中可以使用$event
获取$emit
传递的参数,如果$emit
触发的事件引用的是另外一个事件,那么$emit
的参数将传给它引用的事件(argsCouter
即$emit
传递的参数)
在组件中使用v-model
<input v-model="searchText">
这是一个v-model
的使用场景,那么它的本质其实是
<input :value="searchText" @input="searchText=$event.target.value">
那么在组件上使用v-model
就相当于
<custom-input :value="searchText" @input="$event"></custom-input>
等同于
<custom-input v-model="searchText"></custom-input>
为了让它工作,在组件中首先得把value
绑在prop
上,然后它得触发input
事件
Vue.component('custom-input',{
prps: ['value'],
template: `<input :value="value" @input="$emit('input',$event.target.value)"`
})
插槽
有时候我们在调用组件的时候,组件元素中间还有一些其他的东西,一般情况下,如果不处理这些东西会被舍弃的,但是如果加上<slot>
元素,这些东西就会被渲染在组件之中
<custom-link>
这是一个链接
</custom-link>
那么在组件中,如果要使用这是一个链接
,就需要加<slot>
元素
<a href=''>
<slot></slot> //这是一个链接
</a>
组件元素的闭合标签中间内容就是插槽
,在插槽可以加html
模板,甚至可以加组件
具名插槽
上面的只是一个插槽,那它默认渲染的就是组件中的<slot>
,但是当有多个插槽,并且插槽还有指定位置的时候,就必须要给<slot>
加上name
属性
<div class="container">
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
</div>
那么在父级提供插槽内容的时候,必须加一个<template>
元素包裹
<base-layout>
<template slot="header">
// 这里的内容渲染header
</template>
// 这里的内容没有命名,所以渲染<slot></slot>
<template slot="footer">
// 这里的内容渲染footer
</template>
</base-layout>
卡槽也可以有默认内容的,
<slot>默认内容</slot>
,当父级有传给子组件内容时,会覆盖默认内容
动态组件
前端开发中经常会出现选项卡之类的功能,实现它就需要不同的组件切来切去,这个功能可以通过Vue
的<component>
元素加一个特殊属性is
来实现
<component :is="currentTab"></component>
当点击选项卡的nav
时,只需要把currentTab
指向将要切换的组件名即可
但是这样实现有个问题,就是每次切换都会创建一个组件的实例,我们更多其实是希望它第一次创建以后缓存下来,那么就需要
keep-alive
元素
<keep-alive>
<component :is="currentTab"></component>
</keep-alive>
异步组件
有时候我们加载组件的时候并不希望它一开始就加载进来,而是调用它的时候再加载进来,例如在VueRouter
里面,当我们跳转到路由后再去加载它对应的组件。
components: {
'my-component': () => import('./my-component')
}
// 或者
component(resolve){
require(['@/index.vue'],resolve)
}
使用异步组件的时候,返回的都是一个
promise
函数
结束语
组件相关的东西还有很多很多,比如官网的处理边界情况,只不过应用场景不是很多,所以暂时不深入研究。
就这样,收工。。。
网友评论