01-组件化实现和使用步骤
1.1 什么是组件化?
- 人面对复杂问题的处理方式:
- 任何一个人处理信息的逻辑能力都是有限的
- 所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆的内容
- 但是,我们人有一种天生的能力,就是将问题进行拆解
- 如果将一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现大的 问题也会迎刃而解
- 组件化也是类似的思想:
- 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得变得非常复杂,而且不利于后续的管理以及扩展
- 但如果,我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了
- 在这里插入图片描述
- 我们将一个完整的页面分成很多个组件
- 每个组件都用于实现页面的一个功能块
- 而每一个组件又可进行细分
1.2 组件化思想
-
组件化是Vue.js中的重要思想
- 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
- 任何的应用都会被抽象成一颗组件树
-
组件化思想的应用:
- 有了组件化的思想,我们在以后的开发中就要充分的利用它
- 尽可能的将页面拆分成一个个小的,可复用的组件
- 这样让我门的代码更加方便组织和管理,并且扩展性也更强
1.3 注册组件的基本步骤
- 组件的使用分成三个步骤:
- 创建组件构造器
- 注册组件
- 使用组件
02-组件化的基本使用过程
2.1 代码实战
注:自定义组件一定要在Vue实例创建前创建和注册
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>02-组件化的基本使用过程</title>
</head>
<body>
<div id="app">
<div>
<h2>我是标题</h2>
<p>我是内容,123</p>
<p>我是内容,321</p>
</div>
<hr>
<!-- 3.使用组件-->
<my_cpn></my_cpn>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<!--如果没有安装vue.js,就用下面的代码-->
<!--<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>-->
<script type="text/javascript">
// 1.创建组件构造器
//`` ES6新特性 ``定义字符串,可以不用换行
// extend(传入对象)
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容,hhh</p>
<p>我是内容,aaa</p>
</div>
`
})
// 2.注册组件
// component('使用标签的名字',组件构造器名)
Vue.component('my_cpn',cpnC);
// 下面代码要写在最下面
const vm = new Vue({
el: '#app',
data: {
message: 'hello world!'
}
})
</script>
</body>
</html>
2.2 运行结果
03-全局组件和局部组件
3.1 注册组件步骤解析
- Vue.extend():
- 调用Vue.extend()创建的是一个组件构造器
- 通常在创建组件构造器时,传入template代表我们自定义组件模板
- 该模板就是在使用到组件的地方,要显示的HTML代码
- 事实上,这种写法在Vue2.x的文档中几乎看不到了,它会直接使用语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础
- Vue.component():
- 调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称
- 所以需要传递两个参数:
- 注册组件的标签名
- 组件构造器
- 组件必须挂载在某个Vue实例下,否则它不会生效
3.2 代码实战
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>03-全局组件和局部组件</title>
</head>
<body>
<div id="app">
<h1>app</h1>
<my_cpn></my_cpn>
<my_cpn></my_cpn>
<hr>
</div>
<div id="app2">
<h1>app2</h1>
<my_cpn></my_cpn>
<cpn></cpn>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<!--如果没有安装vue.js,就用下面的代码-->
<!--<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>-->
<script type="text/javascript">
// 1.创建组件构造器
const cpnC = Vue.extend({
template:`
<div>
<h3>我是标题</h3>
<p>我是内容,aaa</p>
<p>我是内容,bbb</p>
</div>
`
})
// 2.注册组件(全局组件,意味着可以在多个Vue实例中使用---实际开发中一般不会有多个Vue实例)
Vue.component('my_cpn',cpnC)
const vm = new Vue({
el: '#app',
data: {
message: 'hello world!'
}
})
const vm2 = new Vue({
el:'#app2',
//局部注册 ,只有app实例用此组件
components:{
//cpn 定义标签
cpn:cpnC
}
})
</script>
</body>
</html>
3.3 运行结果
04-父组件和子组件
4.1 父子组件
- 在前面我们看到组件树:
- 组件和组件之间存在层级关系
- 而其中一种非常重要的关系就是父子组件的关系
- 父子组件错误用法:以子标签的形式在Vue实例中使用
- 因为当子组件注册到父组件的components时,Vue会编译好父组件的模块
- 该模块的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中内容了)
- 是只能在父组件中被识别的
- 类似这种用法,是会被浏览器忽略的
4.2 代码实战
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>04-父组件和子组件</title>
</head>
<body>
<div id="app">
<p>cpn2是cpn1的父组件</p>
<cpn2></cpn2>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<!--如果没有安装vue.js,就用下面的代码-->
<!--<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>-->
<script type="text/javascript">
// 1.创建第一个组件
const cpnC1 = Vue.extend({
template:`
<div>
<h2>我是cpnC1</h2>
<p>我是内容:bbb</p>
</div>
`
})
// 2.创建第二个组件
const cpnC2 = Vue.extend({
template:`
<div>
<h2>我是cpnC2</h2>
<p>我是内容:aaa</p>
<cpn1></cpn1>
</div>
`,
// 引入cpnC1,所以cpnC1是cpnC2的子组件
components:{
cpn1 : cpnC1
}
})
const vm = new Vue({
el: '#app',
data: {
},
// 引入cpnC2,所以cpnC2是Vue实例的子组件
components:{
cpn2 :cpnC2
}
})
</script>
</body>
</html>
4.3 运行结果
05-注册组件的语法糖写法
5.1 代码实战
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>05-注册组件的语法糖写法</title>
</head>
<body>
<div id="app">
<h1>我是全局组件</h1>
<my_cpn></my_cpn>
<hr>
<h1>我是局部组件</h1>
<my_cpn1></my_cpn1>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<!--如果没有安装vue.js,就用下面的代码-->
<!--<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>-->
<script type="text/javascript">
// 1.全局组件注册的语法糖
// 1.创建组件构造器
// 2.注册组件
// 内部自动调用extend()
Vue.component('my_cpn',{
template:`<div>
<h2>我是标题</h2>
<p>我是内容,aaa</p>
<p>我是内容,bbb</p>
</div>`
})
//构建局部组件的语法糖
const vm = new Vue({
el:"#app",
data:{
},
components:{
my_cpn1:{
template:`<div>
<h2>我是标题</h2>
<p>我是内容,ccc</p>
<p>我是内容,ddd</p>
</div>`
}
}
})
</script>
</body>
</html>
5.2 运行结果
06-组件模板抽离的写法
6.1 代码实战
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>06-组件模板抽离的写法</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<hr>
<cpn1></cpn1>
</div>
<!-- 1.script标签抽离,注意类型必须是text/x-template-->
<script type="text/x-template" id="cpn">
<div>
<h2>我是script标签抽离</h2>
<p>我是内容,script type="text/x-template" id="cpn"</p>
<p>我是内容,script type="text/x-template" id="cpn"</p>
</div>
</script>
<!--2.template标签抽离-->
<template id="cpn1">
<div>
<h2>我是template标签抽离</h2>
<p>我是内容,template id="cpn1"</p>
<p>我是内容,template id="cpn1"</p>
</div>
</template>
<script type="text/javascript" src="../js/vue.js"></script>
<!--如果没有安装vue.js,就用下面的代码-->
<!--<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>-->
<script type="text/javascript">
// 1.注册一个全局组件
Vue.component('cpn',{
template:'#cpn'
})
Vue.component('cpn1',{
template:'#cpn1'
})
const vm = new Vue({
el: '#app',
data: {
message: 'hello world!'
}
})
</script>
</body>
</html>
6.2 运行结果
07-为什么组件data必须是函数
7.1 组件的数据
1、组件可以访问Vue实例数据吗?
- 组件是一个单独功能模块的封装:
- 这个模块有属于自己的HTML模板,也应该有属性自己的数据data
- 组件中的数据是保存在哪里呢?顶层的Vue实例中吗?
- 组件中不能直接访问Vue实例中的data
- Vue组件应该有自己保存数据的地方
2、组件数据的存放
- 组件自己的数据存放在哪里呢?
- 组件对象也有一个data属性(也可以有methods等属性)
- 只是这个data属性必须是一个函数
- 而且这个函数的返回一个对象,对象内部保存着数据
3、为什么组件data必须是函数
- 组件的设计,是为了使用模板,多处复用代码,方便快速搭建项目
- 如果我们的data不是一个函数,函数不返回一个对象
- 那么,我们复用的这一类型的所有组件的数据,都会随着一处的更改而全部都改,就没有独立性,这显示不是我们想要的
- 然后我们data使用函数,返回一个对象,对象中装数据
- 这样的话,我们每个组件的数据都会独立开辟一个空间存储自己的数据,组件间相同数据,互不影响
7.2 代码实战
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>07-为什么组件data必须是函数</title>
</head>
<body>
<div id="app">
<my_count></my_count>
<my_count></my_count>
<my_count></my_count>
</div>
<template id="cpn">
<div>
<h2>当前计数:{{counter}}</h2>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script type="text/javascript" src="../js/vue.js"></script>
<!--如果没有安装vue.js,就用下面的代码-->
<!--<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>-->
<script type="text/javascript">
//1.注册全局组件
Vue.component('my_count',{
template:'#cpn',
data(){
return{
counter:0
}
},
methods:{
increment(){
this.counter++
},
decrement(){
this.counter--
}
}
})
const vm = new Vue({
el: '#app',
data: {
}
})
</script>
</body>
</html>
7.3 运行结果
08-父子组件通信-父传子
8.1 父子组件的通信
- 子组件是不能直接引用父组件或者Vue实例的数据的
- 在开发中,往往一些数据确实需要从上层传递到下层
- 比如在一个页面中,我们从服务器请求到了很多数据
- 其中一些数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示
- 这个时候,并不会让子组件再次发送一个一次网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)
- 如何进行父子组件间的通信呢?Vue官方提到
- 通过props向子组件传递数据(父传子)
- 通过自定义事件向父组件发送消息(子传父)
8.2 props数据验证
- props可以是一个数组,也可以是对象
- 当需要对props进行类型验证时,就需要是对象写法了
- 对象写法支持类型:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
- 当我们有自定义构造函数时,验证也支持自定义类型
8.3 代码实战
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>08-父子组件通信-父传子</title>
</head>
<body>
<div id="app">
<my_cpn :cmessage="message" :cmovies="movies"></my_cpn>
</div>
<template id="my_cpn">
<div>
<ul>
<li v-for="movie in cmovies">{{movie}}</li>
</ul>
<p>{{cmovies}}</p>
<p>{{cmessage}}</p>
</div>
</template>
<script type="text/javascript" src="../js/vue.js"></script>
<!--如果没有安装vue.js,就用下面的代码-->
<!--<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>-->
<script type="text/javascript">
// 定义局部组件
const my_cpn ={
template:'#my_cpn',
// 传递数组
// props:['cmovies','cmessage'],
// 传递对象,控制传递类型的限制
// 传递对象,提供一些默认值
props:{
// cmovies:Array,
// cmessage:String,
cmessage:{
type: String, // 限制类型
default:"full", // 设置默认值,默认值在没有传值的情况下,会自动显示默认值
require:false // 如果为True,那么就是必传值,不传就会报错
},
cmovies:{
type:Array,
default(){
return []
}
}
},
data(){
return{
}
}
}
const vm = new Vue({
el: '#app',
data: {
message:'hello',
movies:['海王','海贼王','海尔兄弟'],
},
components: {
my_cpn
}
})
</script>
</body>
</html>
8.4 运行结果
8.5 props驼峰标识
- 如果props使用的驼峰标识,在动态绑定的时候,不能直接绑定,在每个大写字母前面需要加一个-
8.6 代码实战
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>09-props驼峰标识</title>
</head>
<body>
<div id="app">
<cpn :c-info="info"></cpn>
</div>
<template id="cpn">
<h2>{{cInfo}}</h2>
</template>
<script type="text/javascript" src="../js/vue.js"></script>
<!--如果没有安装vue.js,就用下面的代码-->
<!--<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>-->
<script type="text/javascript">
const cpn ={
template:'#cpn',
props:{
//驼峰标识
cInfo:{
type:Object,
default(){
return{}
}
}
}
}
const vm = new Vue({
el: '#app',
data: {
info:{
name:'key',
age:20,
height:1.83
}
},
components:{
cpn
}
})
</script>
</body>
</html>
8.7 运行结果
09-父子组件通信-子传父(自定义事件)
9.1 子级向父级传递
- props用于父组件向子组件传递数据,还有一种比较常见的事子组件传递数据或事件到父组件中
- 我们需要用自定义事件来完成
- 什么时候需要自定义事件呢?
- 当子组件需要向父组件传递数据时,就要用到自定义事件
- v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件
- 自定义事件的流程:
- 在子组件中,通过$emit()来监听事件
- 在父组件中,通过v-on来监听子组件事件
- 例子:
- 我们之前做过两个按钮+1和-1,点击修改counter
- 我们整个操作的过程还是在子组件中完成,但是之后的展示交给父组件
- 这样,我们就需要将子组件中的counter,传给父组件的某个属性,比如total
9.2 代码实战
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>09-父子组件通信-子传父(自定义事件)</title>
</head>
<body>
<!--1.父组件模板-->
<div id="app">
<cpn @itemclick="cpnclick"></cpn>
</div>
<!--2.子组件模板-->
<template id="cpn">
<div>
<!-- 创建分类按钮,并监听点击-->
<button v-for="item in categories" @click="itemclick(item)">
{{item.name}}
</button>
</div>
</template>
<script type="text/javascript" src="../js/vue.js"></script>
<!--如果没有安装vue.js,就用下面的代码-->
<!--<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>-->
<script type="text/javascript">
// 1.子组件
const cpn = {
template: '#cpn',
data(){
return{
categories:[
{id:'100',name:'热门推荐'},
{id:'101',name:'手机数码'},
{id:'102',name:'家用电器'},
{id:'103',name:'电脑办公'},
]
}
},
methods:{
itemclick(item){
console.log('子组件打印',item);
//将item传给父组件,自定义
//this.$emit('名称',传送参数)
this.$emit('itemclick',item)
}
}
}
const vm = new Vue({
el: '#app',
data: {
message: 'hello world!'
},
components:{
cpn
},
methods:{
cpnclick(item){
console.log('父组件cpnclick打印',item);
}
}
})
</script>
</body>
</html>
9.3 运行结果
10-父子组件通信-结合双向绑定
10.1 实现父组件与子组件之间的双向绑定
10.2 代码实战
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>10-父子组件通信-结合双向绑定</title>
</head>
<body>
<div id="app">
<cpn :number1="num1"
:number2="num2"
@num1change="num1change"
@num2change="num2change" >
</cpn>
</div>
<template id="cpn">
<div>
<!-- 使用data中的计算属性进程改动数据,双向绑定-->
<h2>props:{{number1}}</h2>
<h2>data:{{dnumber1}}</h2>
<!-- 单向绑定,只绑定子组件的数据-->
<input type="text" v-model="dnumber1"> 单向绑定,只绑定子组件的数据
<hr>
<!--<input type="text" :value="dnumber1" @input="dnumber1=$event.target.value"> 双向绑定-->
<!-- @input后面太长了 可以写成函数-->
<input type="text" :value="dnumber1" @input="num1Input"> 双向绑定
<h2>props:{{number2}}</h2>
<h2>data:{{dnumber2}}</h2>
<input type="text" v-model="dnumber2"> 单向绑定,只绑定子组件的数据
<hr>
<input type="text" :value="dnumber2" @input="num2Input">双向绑定
<!-- 单向绑定,只绑定子组件的数据-->
<!-- <input type="text" v-model="dnumber2">-->
<!-- 直接绑定,Vue不支持-->
<!-- <h2>{{number1}}</h2>-->
<!-- <input type="text" v-model="number1">-->
<!-- 直接绑定,Vue不支持-->
<!-- <h2>{{number2}}</h2>-->
<!-- <input type="text" v-model="number2">-->
</div>
</template>
<!--<script type="text/javascript" src="../js/vue.js"></script>-->
<!--如果没有安装vue.js,就用下面的代码-->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript">
// 局部组件
const cpn = {
template:'#cpn',
props:{
//如果只是想直接展示的话,就可以直接用
//如果要改数据的话,最好是放在data中用计算属性更改
number1:Number,
number2:Number
},
data(){
return{
dnumber1:this.number1,
dnumber2:this.number2
}
},
methods:{
num1Input(){
this.dnumber1 = event.target.value;
this.$emit('num1change',this.dnumber1)
},
num2Input(){
this.dnumber2 = event.target.value;
this.$emit('num2change',this.dnumber2)
}
}
}
const vm = new Vue({
el: '#app',
data: {
num1:1,
num2:0
},
methods: {
num1change(value){
this.num1 = parseFloat(value)
},
num2change(value){
this.num2 = parseFloat(value)
}
},
components:{
cpn
}
})
</script>
</body>
</html>
10.3 运行结果
11-父组件访问子组件-children-refs
11.1 父子组件的访问方式:$children $refs
- 有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问根组件
- 父组件访问子组件:使用$ children或者$ refs
11.2 代码实战
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>11-父组件访问子组件-children-refs</title>
</head>
<body>
<div id="app">
<cpn ref="aaa"></cpn>
<cpn></cpn>
<cpn ref="ccc"></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>我是子组件</div>
</template>
<script type="text/javascript" src="../js/vue.js"></script>
<!--如果没有安装vue.js,就用下面的代码-->
<!--<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>-->
<script type="text/javascript">
const vm = new Vue({
el: '#app',
data: {
},
methods: {
btnClick(){
// 1. 真实开发中,一般不用$children==========》数组类型
console.log(this.$children);
this.$children[0].showMessage();
console.log(this.$children[0].name);
for (let c of this.$children){
console.log(c);
c.showMessage();
}
// 2.通过$refs拿取数据=====>对象类型,默认是一个空的对象
// console.log(this.$refs);
// console.log('aaa组件',this.$refs.aaa);
// console.log('ccc组件',this.$refs.ccc);
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: "我是子组件的name"
}
},
methods: {
showMessage() {
console.log("showMessage");
}
}
}
}
})
</script>
</body>
</html>
11.3 运行结果
12-子组件访问父组件-parent-root
12.1 子访问父方式
- 有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问根组件
- 子组件访问父组件:使用$ parent
- 子组件访问根组件:使用$ root
12.2 代码实战
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>12-子组件访问父组件-parent-root</title>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是cpn</h2>
<ccpn></ccpn>
</div>
</template>
<template id="ccpn">
<div>
<h2>我是ccpn</h2>
<button @click="btnClick">按钮</button>
</div>
</template>
<script type="text/javascript" src="../js/vue.js"></script>
<!--如果没有安装vue.js,就用下面的代码-->
<!--<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>-->
<script type="text/javascript">
const vm = new Vue({
el: '#app',
data: {
message: 'hello world!'
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: "我是cpn组件的name"
}
},
components: {
//开发中一般不建议这样使用,会让组件不够独立,复用性降低,耦合度太高了
ccpn: {
template: "#ccpn",
methods: {
btnClick() {
//1.访问父组件 $parent
console.log(this.$parent);
console.log(this.$parent.name);
// 2.访问根组件 $root
console.log(this.$root);
console.log(this.$root.message);
}
},
},
}
}
}
})
</script>
</body>
</html>