概述
将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易。
组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树
语法
'Vue2.x 版本之前的写法:'
组件使用三个步骤:
创建组件构造器(调用Vue.extend())
注册组件(Vue.component())
使用组件(在Vue实例的作用范围使用组件,使用自定义的组件名)
例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组件化</title>
</head>
<body>
<div class="app">
<mycpv></mycpv>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建组件构造器
const cpvC = Vue.extend({
template : `
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
`
})
//注册组件
Vue.component('mycpv',cpvC)
const app = new Vue({
el : '.app'
})
</script>
</body>
</html>
'注意:创建组件构造器和注册组件的过程必须放在new Vue 实例前,而且使用组件时,需要放在el选中的元素标签内'
======================================================================
'Vue2.x 版本之后写法,简化了Vue.extend()步骤,直接用对象来替代'
Vue.component('组件名',{
data : () => {
return {
// 存放组件中的数据,data必须是个函数并返回对象
}
},
template : '模板',
methods : '方法'
})
例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="app">
<ccc></ccc>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const cpn = Vue.component('ccc',{
data : () => {
return {
count : 0
}
},
template : `<button @click='add'>点击:{{count}}</button>`,
methods : {
add(){
this.count ++
}
}
})
const app = new Vue({
el : '.app'
})
</script>
</body>
</html>
组件注意事项
1、'注意:创建组件构造器和注册组件的过程必须放在new Vue 实例前,而且使用组件时,需要放在el选中的元素标签内'
2、Vue.component()中的data,必须是个函数并返回一个对象。(一个组件使用多次,并不会共享一个data,而是不同的实例)
3、组件模板内容必须是单个元素(也就是说只能有一个root元素)
例如:
template : `<div><h2></h2></div>`
这样是可以的,但在给div加兄弟元素便报错(此时div便是组件模板的根元素)
4、组件命名规则:
(1)在普通标签中,可以使用短横线命名(<test-one></test-one>)
(2)若使用驼峰命名组件,只能在template中的字符串模板使用,不能当做普通标签使用
全局组件和局部组件
全局组件:可以在多Vue实例中使用
局部组件:只可以在当前Vue实例的组件中使用
例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="app1">
<!-- 全局组件 -->
<gol></gol>
<!-- Vue实例app2的局部组件,在这里不可用 -->
<loc></loc>
</div>
<div class="app2">
<loc></loc>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const cpvG = Vue.extend({
template : `
<h2>我是全局</h2>
`
})
const cpvL = Vue.extend({
template : `
<h2>我是局部</h2>
`
})
// 全局(可以在多个Vue实例下使用)
Vue.component('gol',cpvG)
const app1 = new Vue({
el : '.app1'
})
const app2 = new Vue({
el : '.app2',
components : {
// 局部(只可以在这个Vue实例下使用)
loc : cpvL
}
})
</script>
</body>
</html>
模板的分离写法
可以Vue注册组件时的HTML分离出来写
有如下俩种方法:
1、使用<script type="text/x-template" id='myTemp'></script>
2、<template id='myTemp'></template>
(推荐使用第二种)
例如:
Vue.component('cpnC',{
template : '#myTemp'
})
父子组件
父级组件中调用子级组件,这样简便很多
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="app">
<par></par>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 子组件构造器
const chire = Vue.extend({
template : '<h2>我是子组件</h2>'
})
// 父组件构造器
const pare = Vue.extend({
template :`
<div>
<h2>我是父组件</h2>
<chi></chi>
</div>
` ,
components : {
chi : chire
}
})
let chire = {}
// 注册父组件
Vue.component('par',pare)
const app = new Vue({
el : '.app'
})
</script>
</body>
</html>
父子组件通信
开发中,往往一些数据需要从上层传递到下层,比如一个页面中,我们从服务器请求到了很多数据,但有些数据并非整个页面的大组件来去展示,而是需要下面的子组件进行展示。这个时候并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件),将数据传递给小组件(子组件)。
父子组件通信,俩种方式:
通过props向子组件传递数据
通过事件向父组件发送信息
如下图,父子组件通信:
props(父传子)
props的值有俩种方式:
(1)字符串数组,数组中的字符串就是传递的名称
写法:
props:['data1','data2']
(2)对象,对象可以设置传递时的类型,也可以设置默认值等。还可以自定义类型
'推荐第二种写法'
对象写法可进行类型等验证,验证支持的类型例如:
String
Number
Boolean
Array
Object
Date
Function
Symbol
写法:
props:{
data1 : String,
data2 : Number,
data3 : {
type : String ,
default : '默认值',
required : true
/*必须传值,否则报错*/
}
}
对象写法父传子,例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="app">
<cpn-c :data='message'></cpn-c>
</div>
<template id="temp">
<h2>{{data}}</h2>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let cpn = {
template : '#temp' ,
props : {
data : String
}
}
const app = new Vue({
el : '.app' ,
data : {
message : '数据...'
},
components : {
'cpn-c' : cpn
}
})
</script>
</body>
</html>
自定义事件(子传父)
子级向父级传送数据。
举个应用场景,例如:
商品分类:有热门推荐、手机数码,家用家电、电脑办公等,若将商品分类看作子组件,那么当点击了其中的家用电器,那么它会向父级组件传入点击事件,并把当前点击的那个元素内容告诉父组件,父组件在将对应的应该展示的数据刷新渲染前端
配合下图:
例如,以下代码,通过点击按钮子级商品分类组件,传向父级组件,并打印出当前是点击的那个分类按钮:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 父组件模板 -->
<div class="app" >
<cpn-c @custom_click='printf'></cpn-c>
</div>
<!-- 子组件模板 -->
<template id="temp" >
<div>
<button v-for='iteam in category' @click='send(iteam)'>{{iteam.name}}</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const cpn = {
template : '#temp' ,
data() {
return {
category : [
{id : 1, name :'热门推荐'},
{id : 2, name : '家用电器'},
{id : 3, name : '手机数码'}
]
}
},
methods : {
send(iteam){
// 发射(自定义事件,当前点击的按钮)
this.$emit('custom_click',iteam)
}
}
}
const app = new Vue({
el : '.app' ,
data : {
message: ''
},
components : {
'cpn-c' : cpn
},
methods : {
printf(iteam){
console.log(iteam,'aaa')
}
}
})
</script>
</body>
</html>
结果如下图:
父子组件访问
有时候需要父组件直接访问子组件,或子组件直接访问父组件,或者子组件访问根组件
父组件访问子组件: 使用 $children 或 $refs
'实际开发推荐使用:$refs'
因为在开发中,$refs 最常用,所以举个例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Demo</title>
</head>
<body>
<div class="app">
<cpn ref='obj'></cpn>
<button @click='printf'>按钮获取子级</button>
</div>
<template id="temp">
<div>
<h2>我是子级组件</h2>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el : '.app' ,
methods : {
printf(){
console.log(this.$refs.obj.name)
}
},
components : {
cpn : {
template : '#temp' ,
data(){
return {
name : 'jine'
}
}
}
}
})
</script>
</body>
</html>
子组件访问父组件: 使用 $parent
语法: this.$parent.name
-----------------------------------------
访问root
语法: this.$root.name
'实际开发不常用,因为这样会降低这个组件的耦合性,所以不举例说明'
插槽
组件的插槽是为了让我们封装的组件更加具有扩展性
让使用者决定组件内部的一些内容展示什么
有区别但又有相同共性(可以封装成一个组件,提供插槽)
抽取共性,保留不同(将不同的作成插槽)
1、插槽的基本使用: <solt> </solt>
2、插槽的默认值 : <solt><button>按钮</button></solt>
'若不传值,则有默认值'
'若传一个值,则替换默认值'
'若有多个值,同时放入,那么会这多个值将默认值替换掉
/*
上面说的值是其他元素,例如:span、p 元素标签等,具体看下面代码
*/
例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>插槽</title>
</head>
<body>
<div class="app">
<!-- 无传值,使用slot提供的默认值 -->
<cpn></cpn>
<cpn>
<span>单值:我传了一个值,是span元素</span>
</cpn>
<cpn>
<h2>多值:以下是传了多个值,我是h1元素</h2>
<p>多值:我是p元素</p>
<input type="text" value="多值:我是input元素">
</cpn>
</div>
<template id="temp">
<div>
<h1>我是cnp组件提供的共同元素h1</h1>
<slot>
<button>按钮</button>
</slot>
<br></br>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const cpn = {
template : '#temp' ,
}
const app = new Vue({
el : '.app' ,
components : {
cpn
}
})
</script>
</body>
</html>
具名插槽
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
可以替换指定名字的插槽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>插槽</title>
</head>
<body>
<div class="app">
<cpn>
<span>我替换了默认slot</span>
<button slot="left">我替换了左边</button>
</cpn>
</div>
<template id="temp">
<div>
<slot name='left'>slot左边</slot>
<slot>slot中间</slot>
<slot name=right>slot右边</slot>
<br></br>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const cpn = {
template : '#temp' ,
}
const app = new Vue({
el : '.app' ,
components : {
cpn
}
})
</script>
</body>
</html>
'Vue的版本官方一直在更新变化,其中上面使用的 slot="xx" 以及scope-slot="xx" 在2.6.0+ 中已弃用'
'现版本使用了v-slot 或语法糖 # '
--------------------------------------------------------
语法格式
不简写,不带参数:
v-slot:slotName //slotName不能加双引号“”
匿名插槽在新语法下,默认指向default(可以省略 v-slot:default)
不简写,带参数:
<template v-slot:tit>aa</template>
简写方式:
如果你想使用简写语法,必需指定插值的名字
v-slot:header 简写成 #header
v-slot:default 简写成 #default
简写带参数:
#header='{arg}'
-------------------------------------------------------
'注意事项:'
1、在旧版本中slot="xxx"的情况下,可以挂载在非 template标签上
2、但是在v-slot:tit的情况下,必须使用template标签
-----------------------------------------------------------------
此语法的代码练习,参考下面作用域插槽中的示例代码
作用域插槽
总结一句话:父组件替换插槽的标签,但是内容由子组件来提供
新语法格式为
v-slot:tit="slotProps"
//slotProps为自定义的变量名,指向子组件中的data函数返回值
//将具名插槽赋值,很简洁;
//不仅完成插槽指向,还完成了数据挂载
例如,下面这个代码,综合了具名插槽的新语法和作用域插槽的应用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>v-slot</title>
</head>
<body>
<!-- 父模板 -->
<div class="app">
<cpn>
<!-- v-slot 不带参数 -->
<template v-slot:title><h1>我是新标题</h1></template>
<!-- # 语法糖 -->
<template #test></template>
<!-- # 语法糖 +带参数,此时的user是子模板提供的变量,这个变量指向的子组件的data数据 -->
<template #content='{user}'><h3>{{user}}</h3></template>
<!-- 匿名插槽在新语法下,默认指向default(可以省略 v-slot:default) -->
<button>我替代了默认插槽</button>
</cpn>
</div>
<!-- 子模板 -->
<template id='temp'>
<div>
<h2>组件默认提供h2元素</h2>
<slot name='title'><h3>我是标题</h3></slot>
<slot name='test'><input type="text"></slot>
<slot>默认插槽</slot>
<!-- 带参数绑定,user是自定义变量(供父模板调用),name是子组件的data数据的值 -->
<slot name='content' :user='name'><button>按钮</button></slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const cpn = {
template : '#temp' ,
data () {
return {
name : 'jine'
}
}
}
const app = new Vue({
el : '.app' ,
components : {
cpn
}
})
</script>
</body>
</html>