如果将一个页面中所有的处理逻辑全部放在一起,那么处理起来会非常复杂混乱,而且不利于后续的管理以及扩展。
如果将页面拆分成一个个小的功能块,每个功能块完成属于自己的独立功能,那么整个页面的管理和维护就变得容易了。

在这里插入图片描述
在这里插入图片描述

一个页面可以分为多个组件,每个组件又可以细分

Vue的组件思想

①它提供了一种抽象,让我们开发出一个个独立可复用的小组将来构造我们的应用。
②任何的应用都会被抽象成一棵组件树。

一、注册组件的基本步骤

组件的使用分为三个步骤

  1. 创建组件构造器

  2. 注册组件(全局注册、局部注册)

  3. 使用组件

1.1 调用Vue.extend() API:创建组件构造器:const x = Vue.extend({ })
2.1 调用Vue.component() API:注册组件
3.1 在Vue实例范围内使用组件
1.1.1 创建组件模板:(旧的写法)
x 是构造器名称,在注册是会引用此名称。组件就相当于一个html页面,最外层要用一个div来包裹,然后再是内容。
const x = Vue.extend({
	template:
		`
		<div>
			<h2>我是组件标题</h2>
		</div>
		`
})
2.1.1 全局注册组件: 'my-cpn’是组件的标签名,x 是1.1.1的内容。也就是说,以后用到x 组件时,用<my-cpn></my-cpn>就可以代替了。
Vue.component('my-cpn',x)
3.1.1 在Vue实例中使用:
<div id="app">
	<my-cpn></my-cpn>
</div>

二、创建组件构造器步骤解析

1、Vue.extend() 创建的是一个组件构造器,通常在创建组件的地方,传入template代表自定义组件的模板,该模板就是在使用到组件的地方,要显示的HTML代码。旧写法参考1.1.1内容。
2、Vue.component()要传入两个参数,一个是注册组件的标签名称,一个是组件构造器的名称。如1.1.1中的 x 。
3、可以多重嵌套使用自定义组件,但组件标签中不能有其他内容<my-cpn>用

四、父组件和子组件

先构造两个组件:
第一个:
const cpn1 = Vue.extend({
	template :`<div><h2>组件1</h2></div>`
})
第二个:把组件1在组件2中注册:
const cpn2 = Vue.extend({
	template :`
	<div>
		<h2>组件2</h2>
		<cpn1></cpn1>
	</div>
	`,
	components:{ 
        cpn1:cpn1 // 可以简写为一个cpn1
    } 
})
组件2在实例中注册
const app = new Vue({
      el: "#app",
      components: {
        cpn2: cpn2 // 可以简写为一个cpn1
      },
})
使用:
  <div id="app">
    <cpn1></cpn1> // 未在实例中注册,不可使用
    <cpn2></cpn2> // 已在实例中注册,可以使用
  </div>
其实const app实例也算是一个组件,只不过是页面的最大组件:根组件ROOT,所以vue实例也有template。子组件必须先于父组件创建,否则识别不了子组件。

五、注册组件的语法糖

此语法糖是将创建组件的构造器嵌入注册步骤当中。

原先:遵循1.1->2.1->3.1的顺序

现在 1.1和2.1结合:(全局)

Vue.component('my-cpn',{
	template: `<div><h2>组件标题</h2></div>`
})

(局部)

const app = new Vue({
      el: "#app",
      components: {
        	'my-cpn': {
        		template:`<div><h2>组件标题</h2></div>`
        }
      },
  })

六、组件模板抽离的写法

由于在template中写html语句的便捷性不好,所以将html语句抽离出来写

6.1 写在<script>标签中:

<script type="text/x-template" id="cpn1">
	<div>
		<h2>组件1</h2>
	</div>
</script>
script标签类型为:text/x-template ,并且要指定id,以便于使用
以全局注册为例:
Vue.components( 'my-cpn1' , { template: '#cpn1' })
在实例中使用:
<div id="app">
	<my-cpn1></my-cpn1>
</div>

6.2 写在<template>标签中:

<template id="cpn2">
	<div>
		<h2>组件2</h2>
	</div>
</template>
给template标签加上id,便于使用。
以局部注册为例:
const app = new Vue({
      el: "#app",
      components: {
        	'my-cpn2': {
        		template: '#cpn2'
        }
      },
  })
在实例中使用:
<div id="app">
	<my-cpn2></my-cpn2>
</div>

七、注意点

1、组件内部不能直接访问实例内部数据或方法等。
2、组件是一个单独功能模块的封装,有属于自己的html模板。
3、即使不是直接而是间接地让组件访问到实例中的数据,那么假设一个页面有成千上百个组件,数据全部存放到实例中,会让实例显得臃肿。
4、vue组件要有存放自己数据的地方。

八、组件的数据存放

在注册的时候
<template id="cpn3">
	<div>
		<h2>{{title}}</h2>
	</div>
</template>

Vue.components( 'my-cpn3' , 
	{ 
		template: '#cpn3',
			data() {
				return {
                    title: '组件3'
				}
			}
	})
组件存放自己数据的地方,在与template同级下的 data()函数中,所以需要返回值,这个值是对象类型,与实例中的data:{}不太一样。其实组件的原型是指向vue实例的,所以组件中也有实例的东西,例如生命周期函数等。

九、为什么组件中的data(){}必须是一个函数?

1、若是以对象的形式存放

①先定义个对象:
const obj = {name: "小明", age:18}
②存到data中:
data(){ return obj }

此时已分配内存空间来存这个对象,假设 内存地址:0x100

后续操作:
let obj1 = data() 
let obj2 = data() 
obj1.name = '小红'
console.log(obj1) 
console.log(obj2) 

结果: name: '小红' age :18
      name: '小红' age :18
obj1、obj2指向的是同一片内存空间的内容,obj1.name = '小红‘,相当于修改这个内存地址中的内容,所以即使obj2没有对这个对象进行修改,但是获取到的内容已经改变了。

2、若是以函数形式存放

data(){
	return{
		name: "小明",
		age:18
	}
}
仅是定义和声明,在内存中暂无对象存储的空间,有调用到此函数,再开辟内存空间。
后续操作:
let obj1 = data() 
let obj2 = data() 
obj1.name = '小红'
console.log(obj1) 
console.log(obj2) 

结果: name: '小红' age :18
      name: '小明' age :18
obj1调用一次data(),内存开辟空间存 name、age,假设内存地址为0x100
obj2调用一次data(),内存开辟空间存 name、age,假设内存地址为0x200
每调用一次data(),就会开辟新的空间来存,所以内存地址不一样,也就是说,obj1、obj2只是内容一样,但没有关系的两块内存空间。在后续操作中,obj1修改了name,只是修改了0x100中的数据,obj2还是保持和第一次获取到的内容一致,没有受影响,二者是独立使用data的。保证了组件的 独立性 和 可 复用性。

十、父子组件的通信

1、父组件通过props向子组件传递消息。

2、子组件通过事件向父组件发送消息。

在这里插入图片描述

props的基本用法:从父组件(根组件)传递数据给子组件
10.1 定义一个模板(子组件)
<template id="soncpn">
	<div>
		<p>{{sonmessage}}</p>
		<h2>{{sonmovies}}</h2>
	</div>
</template> 
// 先留两个空位给message和movies
10.2 构造组件,构造器名称:cpn
const cpn = {
     template: "#soncpn",
     props: ['sonmessage','sonmovies']
  }
  // props用来接收父组件传来的数据
10.3 将vue实例视作父组件,将子组件在父组件中注册
const app = new Vue({
      el: "#app",
      data:{
      	message: "hello world!",
      	movies:['电影1','电影2']
      }
      components: {
        	cpn
      },
  })
10.4 传递数据
<div id="app">
	<cpn :sonmessage="message" :sonmovies="movies"></cpn>
</div>

十一、props的第二种写法及类型验证

1、写法

第一种写法:参照10.2

props:['sonmessage','sonmovies']

第二种写法:

props:{
	sonmessage: String,
	sonmovies: Array
}

第二种写法扩展:

props:{
	sonmessage: {
		type: String,
		default: '默认值'
	}
	sonmovies: {
		type: Array,
		default(){
			return []
		}
	}
}
第二种写法是强制规定传入的数据类型,当传入类型定义是数组或是对象时,默认值必须使用工厂函数来指定。

当传入值为多种类型:

type: [String,Number...]

2、props名注意点:驼峰命名要转换成以-连接

props:['sonMessageInfo']
在实例中:
<cpn :son-message-info="xxx"></cpn>
在<template> 中没有关系
<template>
	<div>
		<h2>{{sonMessageInfo}}</h2>
	</div>
</template>

十二、子组件向父组件传递数据(自定义事件)

props的双向绑定

1、子组件通过$emit发送事件和参数。

2、父组件通过 v-on / @ 来监听

如果要对传入的props进行双向绑定,不建议直接 v-model=“props” ,要用data中的一个新变量来代替。这里是举例input输入框的input事件,将动态绑定的value,也就是numberdata,通过$emit传递给父组件,再在父组件模板中监听此事件,并获取到传过来的值,在父组件methods中定义方法,将获取到的值赋给父组件data中数据。

在这里插入图片描述

<body>
  <div id="app">
    <h2>{{homeNum}}</h2>
    <cpn :numberprops="homeNum" @numinput="num1change"></cpn>
  </div>

  <script src="./js/vue.js"></script>

  <template id="cpn">
    <div>
      <h2>子组件:{{numberdata}}</h2>
      <h2>props:{{numberprops}}</h2>
      <input type="text" :value="numberdata" @input="numinput">

    </div>
  </template>

  <script>
    // 3、在页面中使用组件
    const app = new Vue({
      el: "#app",
      data: {
        homeNum: 2
      },
      methods: {
        num1change(value) {
          this.homeNum = parseInt(value)
          // console.log(value)
        }
      },
      components: {
        cpn: {
          template: "#cpn",
          props: {
            numberprops: Number
          },
          data() {
            return {
              numberdata: this.numberprops
            }
          },
          methods: {
            numinput() {
              this.numberdata = event.target.value
              // console.log(this.number)
              this.$emit('numinput', this.numberdata)

            }
          }
        }
      },
    })
  </script>
</body>

总结为4步:

1、子模版添加默认事件。
2、子组件methods中使用$emit来发送自定义事件。
3、父模板中 @自定义事件名=“父组件处理事件名” 来监听。
4、父组件methods中处理: 父组件处理事件名(){ 处理逻辑 }

十三、父子组件的访问方式

通过对象直接访问

父组件访问子组件:$children 、 $refs

要想使用$refs,必须在组件标签中添加ref属性。
// 在父模板中
<div id="app">
	<cpn ref="aaa"></cpn>
	<cpn1 ref="bbb"></cpn1>
	<cpn2></cpn2>
</div>
this.$refs 就可以获取到所有添加了ref属性的子组件
this.$refs.aaa 可以取到具体项

子组件访问父组件:$parent​

父组件中可能包含多个子组件,所以this.$children获取到的是一个数组类型。
this.$parent 是单个对象,它只能获取到上一级组件

补充:获取根实例:this.$root

Logo

前往低代码交流专区

更多推荐