前言
本篇主要介绍组件的使用以及父子组件的传值和事件互相调用。
定义组件
无论那种组件,需要注意的是组件内的data必须为函数,通过函数内return data来实现数据使用;组件的el也必须使用函数
组件命名规范
- 参考官网文档
- 项目实践描述
1.有意义的,如果是业务组件要用业务的名称;如果是ui组件要用ui的名称
2.名称简短
3.具有可读性,便于沟通
4.符合自定义元素规范,避免使用保留字,使用统一的连接符中划线
5.app-作为统一的命名空间,想到其他项目可以共用
页面内定义的组件
- 页面内可以定义全局或者页面组件
// 注册全局组件
Vue.component('my-component', {
template: '<div>A custom component!</div>',
props:[]
})
//局部组件
var app=new Vue({
el:"#app",
components:{
[ 'my-component': {
template: '#myComponent',
data: function() {
return {
msg: 'This is a Component!' //Vue中component的data必须通过function() return
}
},
methods: {
showMsg: function() {
alert(this.msg);
}
}
]
})
//使用template标签实现组件
<template id="myComponent">
<div>This is a component!</div>
</template>
Vue.component('my-component', {
template: '#myComponent',
props:[]
})
单文件组件
- 单独的一个组件作为一个文件,包括该组件需要的样式以及脚本数据等,如果你想组件也使用共用资源或者同类资源管理的方式也是可以的。其他:参考官网介绍
<!-- 单文件组件定义-->
<template>
<div >
<h2>comp的组件</h2>
data
</ul>
</div>
</template>
<script>
export default {
name: 'comp',
data () {
return {
data:'data'
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped less>
</style>
引入组件
引入文件组件,然后在使用的组件内注册该组件即可
<!--> 其他页面使用该组件<-->
<comp></comp>
<script>
import comp from './comp'
export default {
name: 'app',
components:{comp},
data () {
return {
}
}}
</script>
在全局中注册文件组件,就可以直接使用,在components中将所有组件注册一遍,页面中可以直接使用
import Vue from 'vue'
import booklist from './booklist'
import comp from './comp'
const components = [booklist, comp
]
components.map(component => {
Vue.component(component.name, component)
})
export default {
booklist, comp
}
- 组件传值以及绑定方法
组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。父组件可以使用 props 把数据传给子组件。注意:在子组件中定义prop时,使用了camelCase命名法。由于HTML特性不区分大小写,camelCase的prop用于特性时,需要转为 kebab-case(短横线隔开)。例如,在prop中定义的myName,在用作特性时需要转换为my-name。
//组件中声明可以直接使用,组件中直接使用属性名称即可,与data中定义的具有相同的效果,可以直接使用,
但不是同一个数据
props:['attr']
props:['mySize']
//使用的时候 属性显性声明注入,直接冒号绑定数据就可以,如果你不是动态数据的内容,
那么去掉冒号即可(另外需要注意的是如果加了冒号,引号内支持的是js表达式,否则只是字符串)
<comp :message="msg" :my-size='size'></comp>
data () {
return {
msg:'示例的子组件'
}
}
//父组件
<father>
<child :love="msg"></child>
</father>
//子组件
export default {
...
props: ['love']
}
子组件向父组件传递数值,通过event传递
如在par中使用子组件sub,可以使用自定义事件(myClick)来完成:如果你 传递的是多个值,封装到一个对象中。<sub @myClick="handlerClick" />
在sub中使用this.$emit('myClick')
去触发。 par中写好handlerClick(常规的method方法)对应去处理即可。同级组件(非父子组件)数据传递 (另外一种方法是使用vuex)
兄弟组件或者完全不相关的组件通信就需要使用一个你说的 中央事件总线去处理, 这个new Vue()写在一个单独的文件中作为公用的util方法
如 a,b通信, 在a组件的生命周期函数里面去写对应的监听事件如在mounted处理:
//一般定义在公共的脚本中,如果你是spa项目的话,写在main.js文件中,定义一个全局的,获取全局Vue对象
window.eventBus = new Vue();
//a组件, 监听my-event事件,进行对应的处理,组件创建前添加钩子函数
mounted(){
eventBus.$on('my-event', (data) => {
//do something...
})
}
//b组件 比如在某个元素点击后触发 my-event
methods: {
click: function(){
eventBus.$emit('my-event', data); // data是要传递的数据
}
}
- 注意事项
组件中传入的值如果后续发生改变,那么原始注入的值就会发生改变,建议将值使用赋值到另一个变量,不要直接改变注入属性的值。虽然这不会影响原来的。如果使用了v-model的模型,会实时改变到使用这个值的组件。
组件事件触发
单向数据流
在vue2.x之后,组件中是只建议使用单向数据流,也就是props可以父组件向子组件传值之后,通过data的数据项来读取,不建议直接使用prop的值,
- 如果你传值是简单数据类型,那么可以在子组件的data属性中,将data值赋值为传递的属性值。(否则会报错)
<child :status='status'></child>
Vue.component("child",{
template:"<span>{{value}}</span>",
props:{
status
},
data(){
return {
value:this.status
}
}
})
- 如果传值是引用数据类型,那么需要
子组件
props:{[“initCount”}
由于js中对象以及数组是引用类型,所以当prop为对象或者数组时,会有双向绑定的效果,这点有利有弊
数据验证
- 属性传入支持常规的数据格式验证,也支持自定义验证。
常规的类型有String,Number,Boolean,Object,Array,Function ,可以设置多个类型,用数组实现.在其内部是通过insanceof来实现数据类型的判断。
props:{
len :{
type:[String,Number] // 支持数组,存储多类型参数
}
}
- 支持required是否必须字段,支持default设置默认值,dafault为一个值或者返回默认值的方法。
- 支持validator ,对数据的内容进行严格的校验,可能是长度,格式,具体内容等,返回是否符合结果的布尔型结果即可。
动态组件
有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里:
上述内容可以通过 Vue 的 元素加一个特殊的 is 特性来实现:如果你希望组件不会每次都渲染、销毁,那么你可以设置v-once .其中两个组件的属性注入都可以写入。
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>
在上述示例中,currentTabComponent 可以包括已注册组件的名字,或一个组件的选项对象。
<template>
<component :is="type" v-bind="linkProps(to)">
<slot />
</component>
</template>
<script>
import { isExternal } from '@/utils/validate'
export default {
props: {
to: {
type: String,
required: true
}
},
computed: {
isExternal() {
return isExternal(this.to)
},
type() {
if (this.isExternal) {
return 'a'
}
return 'router-link'
}
},
methods: {
linkProps(to) {
if (this.isExternal) {
return {
href: to,
target: '_blank',
rel: 'noopener'
}
}
return {
to: to
}
}
}
}
</script>
异步加载组件
如何缓存组件
使用keep-alive抽象组件,并不会真正渲染到dom中
应用场景:多数在tab切换中
Vue组件抽离公共逻辑
查看Mixins
插槽分析内容
<alert-box>
Something bad happened.
</alert-box>
基本使用
首先需要在组件定义的时候,定义好插槽的位置。那么引用时
Vue.component("link",{
template:"<a v-bind:href="url" class="nav-link"> <slot></slot></a>"
})
具名插槽 ,允许你将多个插槽通过name区分
//模板
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
//使用时
<base-layout>
<template slot="header">
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template slot="footer">
<p>Here's some contact info</p>
</template>
</base-layout>
默认内容
可以在插槽内写好内置内容,支持传入新内容时覆盖。
<button type="submit">
<slot>Submit</slot>
</button>
编译作用域
该插槽可以访问跟这个模板的其它地方相同的实例属性 (也就是说“作用域”是相同的)。但这个插槽不能访问 的作用域。例如尝试访问 url 是不会工作的。牢记一条准则:
父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译
<navigation-link url="/profile">
Logged in as {{ user.name }}
</navigation-link>
想要插槽内容提供数据的时候,可以通过常规的属性绑定实现:
<ul>
<li
v-for="todo in todos"
v-bind:key="todo.id"
>
<!-- 我们为每个 todo 准备了一个插槽,-->
<!-- 将 `todo` 对象作为一个插槽的 prop 传入。-->
<slot v-bind:todo="todo">
<!-- 回退的内容 -->
{{ todo.text }}
</slot>
</li>
</ul>
那么进一步,如果我们想实现自定义的模板字符串,可以通过template,以及slot-scope实现插槽内容。(在 2.5.0+,slot-scope 不再限制在 元素上使用,而可以用在插槽内的任何元素或组件上)
<todo-list v-bind:todos="todos">
<!-- 将 `slotProps` 定义为插槽作用域的名字 -->
<template slot-scope="slotProps">
<!-- 为待办项自定义一个模板,-->
<!-- 通过 `slotProps` 定制每个待办项。-->
<span v-if="slotProps.todo.isComplete">✓</span>
{{ slotProps.todo.text }}
</template>
</todo-list>
更进一步,在支持es2015的浏览器可以用结构拿到某些对应的参数。这样可以使得插槽内容更纯粹,只保留有用的数据。
<todo-list v-bind:todos="todos">
<template slot-scope="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>