vue初学——深入理解vue组件

Posted try0929

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue初学——深入理解vue组件相关的知识,希望对你有一定的参考价值。

深入理解vue组件

1、细节点一,vue组件和h5规范冲突

举个例子比较能说明问题:

  • 现在是要写个表格,不用组件的形式如下:
<table>
    <tbody>
    	<tr><td>1</td></tr>
    </tbody>
</table>
  • 要使用组件化,就创建一个全局组件
Vue.component(‘row‘,{
			template: ‘<tr><td>1</td></tr>‘
		})
  • 然后就在表格里用row来代替:
<table>
		<tbody>
			<row></row>
			<row></row>
			<row></row>
		</tbody>
</table>
  • 结果从页面上来看是没有问题,但是f12打开看代码却出现了问题,row创建的代码并没有在表格里,而是在表格外面,可以看到严重损坏了模块性:

技术图片

  • 而这是为什么呢,是由于h5规定在tbody下只能装tr,而这里的row让浏览器无法正确渲染

如何解决?(is语法)

  • 这里可以依然用tr标签,但在里面用vue的is语法绑定表示这个原件实际上就是子组件创建的
<table>
	<tbody>
		<tr is=‘row‘></tr>
	</tbody>
</table>

这样就解决了这个问题了。

  • 实际上,在其他h5中规定了只能接某种标签时,也可以用这种方式;如ol和ul中的li,select中的option。

2、子组件定义data不能定义成对象,只能定义成函数

  • 如题,这是由于,子组件通常会被调用多次,而根组件一般只会被调用一次,要保证子组件中的数据不会发生冲突 应该用两套数据,注意这个函数返回一个对象,对象里面保存着数据

3、ref的使用

  • 先用两个子组件分别实现两个计数器:
Vue.component(‘counter‘, {
			template: ‘<div @click="handleClick">{{ number }}</div>‘,
			data: function() { //记得子组件里data要是函数
				return {
					number: 0
				}
			},
			methods: {
				handleClick: function() {
					this.number++;
				}
			}
		})
  • 现在要在外面设置一个变量来实时监控这两个计数器的值,算出总和。那也就是说要将值传入根组件,之前有提到过,也就是要**子组件的事件被触发时用this.(emit(‘事件名‘)来触发根组件的事件**。这里就在handleClick里加上一句``this.)emit(‘change‘);``,即在根组件里触发了change事件。
  • 触发事件之后如何计算total?由于是每次number变化就要计算一次total,所以可以在子组件中用ref来表示一个子组件的引用,而在根组件的实例中可以通过this.$refs.ref名来获得子组件的一个引用。获得引用之后就好说了,直接再在后面添加属性就能获得,代码如下:
//在两个子组件用ref引用
<counter @change=‘handleChange‘ ref=‘one‘></counter>
<counter @change=‘handleChange‘ ref=‘two‘></counter>
//接着在根组件中添加方法来处理数据
handleChange: function() {
					this.total = this.$refs.one.number + this.$refs.two.number;
				}
  • ref加在dom元素上是直接获得该dom元素,加在子组件上是获得该子组件的一个引用

  • 之前做TodoList的时候也提到过要从子组件向根组件传值,那个时候用的是函数的参数传递,而这里是直接获得了子组件的引用 更加方便。之前也提到,根组件向子组件传值是要在子组件里设置一个props属性来存放数据。

4、父子组件数据传递

1)父组件->子组件,通过属性传值

  • 单向数据流:子组件不要自行随意修改父组件传递过来的值,因为若是传的如对象这样的值,修改了一次 在其他子组件中也修改了
  • 那如何解决,很简单,直接在data中创建一个父组件传来的值的副本即可,修改自己的数据并不会影响原来的数据
<div id=‘root‘>
	<counter :count="0"></counter>
	<counter :count="0"></counter> //通过v-bind绑定数据
</div>
var counter = {
    props: [‘count‘],
    data: function() { //记得要是函数哦
        return {
            number: this.count
        }
    },
    template: ‘<div @click="handleClick">{{number}}</div>‘,
    methods: {
        handleClick: function() {
            this.number++; //这里用自己创造的副本来改变
        }
    }
}
var vm = new Vue({
    el: ‘#root‘,
    components: {//记得局部组件要在父组件内注册
        counter: counter 
    }
})

2)子组件->父组件,通过触发事件

  • 子组件有值发生变化时,就触发父组件的事件(可以自己命名),然后在父组件内监听该事件即可;
  • 在触发父组件事件时,后面那个参数就是传进父组件事件处理函数的参数:
//在上面的handleClick中加上
this.$emit(‘change‘, 1); //后面这个参数是用来传值的
//在父组件内监听
<counter :count="0" @change=‘handleChange‘></counter>
<counter :count="0" @change=‘handleChange‘></counter>
//父组件内处理
handleChange: function(step) {
    this.total += step;
}

5、组件参数校验

  • 之前说到,父组件传递过来的值,子组件要设置一个props属性来接收,而这个props属性的值可以不一定要为数组,也可以是一个对象,可以限制父组件传值的范围:
var child = {
			props: {
				content: {
					type: [String], //限制了只能是字符串
					required: true, //必须要传值
					default: ‘Hello, world‘, //默认字符串为这个
					validator: function(value) { //限制长度等
						return value.length>5;
					}
				}
			},
			template: ‘<div>{{content}}</div>‘
		}

  • 子组件中的props是用来接收值的,props中没有这个名字则说明是非props特性,可以把它和之前的自定义属性看作是一样的,所以 props特性用来传值并不会显示在html代码中,而非props特性的属性从而会显示在html中。

6、给组件绑定原生事件

  • 在子组件里绑定事件,都是写在子组件的template里,这里就是默认绑定的原生事件,因为是绑定到了原生dom上
  • 那之前在 比如说 <counter></counter> 里绑定的事件,就是自定义的事件,若是写<counter @click=‘handleClick‘></counter>,这样绑定后点击并不能触发原生的click事件,这是由于counter不是原生dom;
  • 解决方式也很简单,在上面直接写<counter @click.native=‘handleClick‘></counter>即可。

7、非父子组件传值(Bus/总线/发布订阅模式/观察者模式)

  之前组件间传值都是在父子组件(详见上面),那非父子组件如何传值呢?一层一层传未免过于复杂,这里介绍一种Bus方式,这里要实现:点击其中一个子组件,另一个子组件也要变成这个样子。

  • 首先给Vue类的prototype的bus属性创建一个Vue实例,所以这个bus也是Vue的一个实例;

  • 依然给子组件的template添加click事件,而在这个回调函数里触发bus的change事件,其中传参为当前的ct

  • Vue的所有实例都有bus属性,所以触发了change事件所有子组件都能感知到,就在子组件内利用生命周期钩子mounted来让Vue实例的属性bus来接收change事件,这里传入了一个参数,就可以用来改变子组件的值;mounted函数是在 挂在子组件之后 调用的函数。

	<div id="root">
		<child :content=‘"TRY"‘></child>
		<child :content=‘"ARASHI"‘></child>
	</div>
	<script>
		Vue.prototype.bus = new Vue();
		Vue.component (‘child‘, {
			props: [‘content‘],
			data: function() {
				return {
					ct: this.content
				}
			},
			template: ‘<div @click="handleClick">{{ct}}</div>‘,
			methods: {
				handleClick: function() {
					this.bus.$emit(‘change‘,this.ct);
				}
			},
            //mounted生命周期钩子 在挂载子组件之后自动调用
			mounted: function() {
				var _this = this; //要保存这个this,不然后面函数会找不到这个指向
				this.bus.$on(‘change‘, function(value){
					_this.ct = value;
				})
			}
		})
		var vm = new Vue({
			el: ‘#root‘
		})
	</script>

8、vue中的插槽

  从之前的介绍中可知,可以用template来初始化子组件,但如果一切都在template中写 阅读性会十分低,有一种方式可以直接在html的子组件标签内部插入内容,这就是插槽(slot)。使用方法也不难,就直接在下面代码里讲:

//html部分
<div id="root">
    <child>
        <div slot=‘header‘>传进来的header</div>
        <div slot=‘footer‘>传进来的footer</div>
    </child>
</div>
//子组件部分
Vue.component(‘child‘,{
			template: `<div>
							<slot name=‘header‘></slot>
							<div>模板的内容</div>
							<slot name=‘footer‘></slot>	
							<slot name=‘default‘>默认内容</slot>
						</div>`
		})
		var vm = new Vue({
			el: ‘#root‘
		})

定义域插槽

  • 通常出现在需要循环显示的时候,目的是不在子组件template中将循环的每一项的格式(如p h1等)规定,而是在html中再来具体规定;
  • 在template中规定好循环的数组,以及用v-bind定好属性等于循环的每个元素,再在html中子组件具体例化的里面用template标签(后台不会显示的)设置属性slot-scope的值来接收来自子组件传来的值,里面就是具体格式,看代码吧说着好难懂...
<div id="root">
		<child>
    		//标准格式 就是要用template标签 加上属性值设定来获取子组件传来的值,这个prop可以随便设置,只要后面{{}}里用它即可
			<template slot-scope=‘prop‘>
				<h1>{{prop.it}}</h1>
			</template>
		</child>
	</div>
	<script>
		Vue.component(‘child‘,{
			data: function() {
				return {
					list: [4,3,2,1]
				}
			},
            //template里面的:it=item是为了让父组件接收到循环的值
			template: `	<div>
							<slot 
								v-for="item of list"
								:it=item
							></slot>
						</div>`
		})
		var vm = new Vue({
			el: ‘#root‘
		})
   </script>

9、动态组件与v-once指令

动态组件(is)

  • 现在是有两个组件,每次只显示一个,点击一次下面的button就切换到另一个,最容易想到的写法:
	<div id="root">
		<child-one v-if="type===‘child-one‘"></child-one>
		<child-two v-if="type===‘child-two‘"></child-two>
		<button @click=‘handleBtnClick‘>change</button>
	</div>
	<script>
		Vue.component(‘child-one‘,{
			template: ‘<div>child-one</div>‘
		})
		Vue.component(‘child-two‘,{
			template: ‘<div>child-two</div>‘
		})
		var vm = new Vue({
			el: ‘#root‘,
			data: {
				type: ‘child-one‘
			},
			methods: {
				handleBtnClick: function() {
					this.type = (this.type===‘child-one‘?‘child-two‘:‘child-one‘);
				}
			}
		})
	</script>

  • 可以看到,在html中是在子组件实例中用到了v-if来判断这次使用哪一个组件,现在可以用动态组件来实现,同样的代码,只需将两行组件实例给删掉,取而代之用<component :is=‘type‘></component>
  • 还记得之前讲过的is属性的用法吗?就是可以直接将该元素绑定到不同的子组件上,而在这里,绑定到哪个子组件上就由type决定。

v-once指令(提高代码效率)

  • 每次浏览器在渲染页面时,上面这样切换时都会将其中一个子组件删掉然后添加上另外一个,每次这样重新渲染再删去效率不太高,所以引入了v-once;
  • 最开始的代码不要变,只是在两个子组件的template里加上 v-once即可,这样以后,每次渲染新的子组件时就会将该子组件放入内存,等到下次又需要它了,并不需要重新创建一个子组件,直接从内存取出来之前的即可,提高了静态内容的展示效率

以上是关于vue初学——深入理解vue组件的主要内容,如果未能解决你的问题,请参考以下文章

Vue生命周期详解- 单个组件

深入理解vue中Props属性

Vue学习——组件基础

vue递归组件的一些理解

深入理解Vue中的Typescript

Vue keep-alive深入理解及实践总结