组件化开发
在实际开发中,一个页面的功能可能及其复杂,我们需要将其拆分成各个功能模块组件,以此达到复用和方便管理的目的。
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
示例1.1:不使用组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>不使用模板</title>
</head>
<body>
<div id="app">
<h2>标题</h2>
<p>内容一</p>
<p>内容二</p>
<p>内容三</p>
<h2>标题</h2>
<p>内容一</p>
<p>内容二</p>
<p>内容三</p>
<h2>标题</h2>
<p>内容一</p>
<p>内容二</p>
<p>内容三</p>
<h2>标题</h2>
<p>内容一</p>
<p>内容二</p>
<p>内容三</p>
</div>
</body>
</html>
示例1.2:使用组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>组件化的基本使用</title>
</head>
<body>
<div id="app">
<!-- 3.使用组件-->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
</div>
</body>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件
const cpnC = Vue.extend({
template: `
<div>
<h2>标题</h2>
<p>内容一</p>
<p>内容二</p>
<p>内容三</p>
</div>`
})
// 2.注册组件
Vue.component('my-cpn', cpnC)
const app = new Vue({
el: "#app",
})
</script>
</html>
全局组件和局部组件
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>全局组件和局部组件</title>
</head>
<body>
<div id="app">
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<mycpn2></mycpn2>
</div>
<br>
<div id="app2">
<mycpn2></mycpn2>
<mycpn2></mycpn2>
</div>
</body>
<script src="../js/vue.js"></script>
<script>
const cpnC = Vue.extend({
template: `
<div>
<h2>标题1</h2>
</div>`
})
const cpnC2 = Vue.extend({
template: `
<div>
<h2>标题2</h2>
</div>`
})
//全局组件
Vue.component('my-cpn', cpnC)
const app = new Vue({
el: '#app',
})
const app2 = new Vue({
el: '#app2',
components: {
// 局部组件
mycpn2: cpnC2
}
})
</script>
</html>
父组件和子组件
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>父组件和子组件</title>
</head>
<body>
<div id="app">
<parentc></parentc>
</div>
</body>
<script src="../js/vue.js"></script>
<script>
// 1.子组件
const sonC = Vue.extend({
template: `<div>
<h2>子组件</h2>
</div>`
})
// 2.父组件
const parentC = Vue.extend({
template: `
<div>
<h2>父组件</h2>
<sonc></sonc>
</div>`,
components: {
// 3.将子组件注册到父组件中
sonc: sonC
}
})
// vue实例对象可以看做最顶层的组件,root
const app = new Vue({
el: '#app',
components: {
// 4.将父组件注册到实例对象
parentc: parentC
}
})
</script>
</html>
组件注册语法糖写法
示例1:语法糖写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>组件注册语法糖</title>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
</body>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
components: {
'cpn': {
template: `
<div>
<h2>标题</h2>
</div>`
}
}
})
</script>
</html>
示例2:将模板抽离
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>分离写法</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn2></cpn2>
</div>
<!--1.使用script标签,注意:类型必须是text/x-template-->
<script type="text/x-template" id="cpn">
<div>
<h2>标题1</h2>
</div>
</script>
<!--2.使用template标签-->
<template id="cpn2">
<div>
<h2>标题2</h2>
</div>
</template>
</body>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
components: {
cpn: {
template: cpn
},
cpn2: {
template: cpn2
}
}
})
</script>
</html>
组件中的data问题
示例1:组件中的data存放
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>组件中的data存放</title>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
</body>
<template id="cpn">
<div>
<h2>{{title}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 注册一个全局组件
Vue.component('cpn', {
template: '#cpn',
data() {
return {
title: '我是标题'
}
}
})
const app = new Vue({
el: "#app",
})
</script>
</html>
示例2:为什么组件中的data需要是一个函数
当data是一个函数时:
此时,返回一个对象,由于地址值不同,所以组件之间的数据互不干扰
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>data为什么是个函数</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
</div>
</body>
<template id="cpn">
<div>
<h2>当前计数:{{counter}}</h2>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 修改计数器案例,注册组件
Vue.component('cpn', {
template: '#cpn',
data() {
return {
counter: 0
}
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter--
}
}
})
const app = new Vue({
el: '#app'
})
</script>
</html>
当data是一个普通对象时:
对象的地址值不会变化,组件之间公用同一套数据,实际过程中产生问题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>data为什么是个函数</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
</div>
</body>
<template id="cpn">
<div>
<h2>当前计数:{{counter}}</h2>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const obj = {
counter: 0
}
// 修改计数器案例,注册组件
Vue.component('cpn', {
template: '#cpn',
data() {
return obj
},
methods: {
increment() {
this.counter++
},
decrement() {
this.counter--
}
}
})
const app = new Vue({
el: '#app'
})
</script>
</html>
父子组件通信问题
父传子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>父传子</title>
</head>
<body>
<div id="app">
<!-- v-bind不支持驼峰,要使用驼峰时用“-”连接,例如cMessage => c-message -->
<cpn :cmovies="movies" :cmessage="message"></cpn>
</div>
</body>
<template id="cpn">
<div>
<h2>cmovies: {{cmovies}}</h2>
<h2>cmessage: {{cmessage}}</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: '#cpn',
props: ['cmovies', 'cmessage'],
}
const app = new Vue({
el: '#app',
data: {
message: 'hello Vue',
movies: ['海贼王', '海尔兄弟', '海王']
},
components: {
cpn
}
})
</script>
</html>
子传父
示例:通过$emit自定义事件传值
自定义事件传值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>子传父</title>
</head>
<body>
<div id="app">
<cpn @item-click="cpnClick"></cpn>
</div>
</body>
<template id="cpn">
<div>
<button v-for="item in movies" @click="btnClick(item)">{{item.name}}</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: '#cpn',
data() {
return {
movies: [
{id: 0, name: '海贼王'},
{id: 1, name: '海王'},
{id: 2, name: '海尔兄弟'},
{id: 3, name: '海下两万米'},
]
}
},
methods: {
btnClick(item) {
this.$emit('item-click', item)
}
}
}
const app = new Vue({
el: '#app',
components: {
cpn
},
methods: {
cpnClick(item) {
console.log(item)
}
}
})
</script>
</html>
父组件调用子组件方法-$children
$children返回的是一个数组,返回的是当前组件的全部子组件的信息,可以通过$children[i].mthod的方法调用,也就是数组中的array[i].method的形式,不过开发中由于需求变动较大,这种方法不够灵活,因此使用的不多。
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>父访子-children</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn2></cpn2>
<button @click="btnClick">子组件信息</button>
</div>
</body>
<template id="cpn">
<div>
<h2>子组件1</h2>
</div>
</template>
<template id="cpn2">
<div>
<h2>子组件2</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
methods: {
btnClick() {
console.log(this.$children)
for (const child of this.$children) {
child.showMessage()
}
}
},
components: {
cpn: {
template: "#cpn",
data() {
return {
name: '子组件1'
}
},
methods: {
showMessage() {
console.log('我是' + this.name)
}
}
},
cpn2: {
template: "#cpn2",
data() {
return {
name: '子组件2'
}
},
methods: {
showMessage() {
console.log('我是' + this.name)
}
}
}
}
})
</script>
</html>
父组件调用子组件-$refs
$refs返回的是一个对象,因此可以通过调用对象内部的方法使用。如下列的例子,在使用控件的时候在控件加上"ref = abc",本质上类似于div标签中的id,因此可以通过this.$refs.abc找到在当前组件中使用的命名为abc的子组件。此时使用this.$refs.adc.method调用子组件方法。该方法类似于对象中的通过名称绑定值,因此即便开发过程中有变动也没啥影响,用的比较频繁。
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>父访问子-ref</title>
</head>
<body>
<div id="app">
<cpn2></cpn2>
<cpn ref="abc"></cpn>
<cpn2 ref="bcd"></cpn2>
<button @click="btnClick">按钮</button>
</div>
</body>
<template id="cpn">
<div>
<h2>子组件1</h2>
</div>
</template>
<template id="cpn2">
<div>
<h2>子组件2</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
methods: {
btnClick() {
console.log(this.$refs)
console.log(this.$refs.abc.name)
this.$refs.abc.showMessage()
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '子组件1'
}
},
methods: {
showMessage() {
console.log('我是' + this.name)
}
}
},
cpn2: {
template: '#cpn2',
data() {
return {
name: '子组件2'
}
},
methods: {
showMessage() {
console.log('我是' + this.name)
}
}
}
}
})
</script>
</html>
子组件访问父组件和根组件-$parent && $root
$parent可以用来访问该组件的父组件,不过会加强和父组件之间的耦合性,一般不推荐使用
$root可以用来直接访问根组件
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>子访问父-parent与root</title>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
</body>
<template id="cpn">
<div>
<h2>{{name}}</h2>
<cpn2></cpn2>
</div>
</template>
<template id="cpn2">
<div>
<h2>{{name}}</h2>
<button @click="showParent">访问父组件</button>
<button @click="showRoot">访问根组件</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
let cpn2 = {
template: '#cpn2',
data() {
return {
name: '子组件'
}
},
methods: {
showParent() {
console.log(this.$parent)
console.log(this.$parent.name)
},
showRoot() {
console.log(this.$root)
console.log(this.$root.name)
}
}
}
let cpn = {
template: '#cpn',
data() {
return {
name: '父组件'
}
},
components: {
cpn2
}
}
const app = new Vue({
el: '#app',
data: {
name: '根组件'
},
components: {
cpn
}
})
</script>
</html>
slot插槽
原始组件的可扩展性太差,vue中提供了一种方法。slot插槽可以用于对组件的扩展
插槽的详细使用
slot的基本使用
例如有以下需求:
- 第一个子组件需要加一个按钮
- 第二个子组件需要加一个span标签
- 第三个子组件不需要改动
此时可以在组件中添加slot用于可能产生的替换
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>slot的基本使用</title>
</head>
<body>
<div id="app">
<cpn>
<button>按钮</button>
</cpn>
<cpn>
<span>span</span>
</cpn>
<cpn></cpn>
</div>
</body>
<template id="cpn">
<div>
<h2>子组件</h2>
<slot></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
components: {
cpn: {
template: '#cpn',
}
}
})
</script>
</html>
slot的默认值
slot中可以设置默认值
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>slot的基本使用</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn>
<span>span</span>
</cpn>
</div>
</body>
<template id="cpn">
<div>
<h2>子组件</h2>
<slot>
<button>按钮</button>
</slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
components: {
cpn: {
template: '#cpn',
}
}
})
</script>
</html>
具名插槽
有一个需求:
- 将一个div分成三块
- 中间改为标题
- 左边改为返回按钮
- 右边改为前进按钮
示例1:不使用具名
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>具名插槽</title>
</head>
<body>
<div id="app">
<cpn>
<span>标题</span>
</cpn>
</div>
</body>
<template id="cpn">
<div>
<slot><span>左边</span></slot>
<slot><span>中间</span></slot>
<slot><span>右边</span></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
components: {
cpn: {
template: '#cpn'
}
}
})
</script>
</html>
此时使用会将所用的未使用具名的slot都替换
示例2:使用具名完成需求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>具名插槽</title>
</head>
<body>
<div id="app">
<cpn>
<button slot="left">返回</button>
<span slot="center">标题</span>
<button slot="right">前进</button>
</cpn>
</div>
</body>
<template id="cpn">
<div>
<slot name="left"><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot name="right"><span>右边</span></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
components: {
cpn: {
template: '#cpn'
}
}
})
</script>
</html>
编译作用域
简单来说就是这个组件在哪使用就只能访问那个实例对象中的内容,例如下面的例子中,cpn组件在app中使用,cpn只能访问app.data中的message,而cpn.data的name无法访问。
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>编译作用域</title>
</head>
<body>
<div id="app">
<cpn>
<p>{{name}}</p>
<p>{{message}}</p>
</cpn>
</div>
</body>
<template id="cpn">
<div>
<h2>子组件</h2>
<slot></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: 'vue'
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '子组件'
}
}
}
}
})
</script>
</html>
作用域插槽
简单来讲就是通过插槽获取子组件中的数据
一个例子:
- 在父组件中将以下组件中的标签修改为h2
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>作用域插槽</title>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
</body>
<template id="cpn">
<div>
<slot>
<ul>
<li v-for="item in Planguages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
components: {
cpn: {
template: '#cpn',
data() {
return {
Planguages: ['Java', 'JavaScript', 'C++', 'C#', 'Go']
}
}
}
}
})
</script>
</html>
示例2:修改后
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>作用域插槽</title>
</head>
<body>
<div id="app">
<cpn>
<!-- <template v-slot="slot"> 2.6以上推荐使用-->
<template slot-scope="slot">
<h2 v-for="item in slot.planguages">{{item}}</h2>
</template>
</cpn>
</div>
</body>
<template id="cpn">
<div>
<slot :planguages="Planguages">
<ul>
<li v-for="item in Planguages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
components: {
cpn: {
template: '#cpn',
data() {
return {
Planguages: ['Java', 'JavaScript', 'C++', 'C#', 'Go']
}
}
}
}
})
</script>
</html>