Vue组件通信的其他方式

我心飞翔 分类:vue

Vue组件通信的其他方式

一、序言

总结一下前面介绍的组件通信的3种方式:

  • 父组件通过prop向子组件传递数据。
  • 子组件通过自定义事件向父组件发起通知或进行数据传递。
  • 子组件通过<slot>元素充当占位符,获取父组件分发的内容;也可以在子组件的<slot>元素上使用v-bind指令绑定一个插槽prop,向父组件提供数据。
    现在介绍组件通信的其他实现方式

二、组件通信的其他实现方式

2.1 访问根实例

  在每一个根组件实例的子组件中,都可以通过$root属性访问根实例。例如:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<div id="app">
			<parent></parent>
		</div>
		
	    <script src="https://unpkg.com/vue@next"></script>
        <script>
            const app = Vue.createApp({
                data() {
                    return {
                       price: 188 
                    }
        	    },
        	    computed: {
        	  	    totalPrice(){
        	  		    return this.price * 10;
        	  	    }
        	    },
        	    methods: {
        	  	    hello(){
        	  		    return "Hello, Java无难事";
        	  	    }
        	    }
            })
            
        	app.component('parent', {
        		template: '<child></child>'
        	})
        	
        	app.component('child', {
        	 	methods: {
        	 		accessRoot(){
        	 			console.log("单价:" + this.$root.price);
        	 			console.log("总价:" + this.$root.totalPrice);
        	 			console.log(this.$root.hello());
        	 		}
        	 	},
        		template: '<button @click="accessRoot">访问根实例</button>'
        	})

        	app.mount('#app');
        </script>
	</body>
</html>

  在浏览器单击“访问根实例”按钮,Console窗口中输出如下:

Vue组件通信的其他方式

  不管组件是根实例的子组件,还是更深层级的后代组件,$root属性总是代表了根实例。

2.2 访问父组件实例

  与root类似,parent属性用于在一个子组件中访问父组件的实例,这可以替代父组件通过prop向子组件传数据的方式。 例如:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<div id="app">
			<parent></parent>
		</div>
		
	    <script src="https://unpkg.com/vue@next"></script>
        <script>
            const app = Vue.createApp({});
        	app.component('parent', {
        		data(){
        			return {
        				price: 188
        			}
        	    },
        	    computed: {
        	  	    totalPrice(){
        	  		    return this.price * 10;
        	  	    }
        	    },
        	    methods: {
        	  	    hello(){
        	  		    return "Hello, Java无难事";
        	  	    }
        	    },
        		template: '<child></child>'
        	})
        	
        	app.component('child', {
        	    methods: {
                    accessParent(){
                        console.log("单价:" + this.$parent.price);
                        console.log("总价:" + this.$parent.totalPrice);
                        console.log(this.$parent.hello());
                    }
                },
        		template: '<button @click="accessParent">访问父组件实例</button>'
        	})
            
            app.mount('#app')
        </script>
	</body>
</html>
Vue组件通信的其他方式

$parent属性只能用于访问父组件实例,如果父组件之上还有父组件,那么该组件是访问不到的。

2.3 访问子组件实例或子元素

  现在反过来,如果父组件要访问子组件实例,应该怎么办?在Vue.js中,父组件要访问子组件实例或子元素,可以给子组件或子元素添加一个特殊的属性ref,为子组件或子元素分配一个引用ID,然后父组件就可以通过$refs属性访问子组件实例或子元素。代码示例如下:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<div id="app">
			<parent></parent>
		</div>
	
	    <script src="https://unpkg.com/vue@next"></script>
		<script>
		    const app = Vue.createApp({});
			app.component('parent', {
			    mounted(){
			  	    // 访问子元素<input>,让其具有焦点
			  	    this.$refs.inputElement.focus();
			  	    // 访问子组件<child>的message数据属性
			  	    console.log(this.$refs.childComponent.message)
			    },
				template: `
					<div>
						<input ref="inputElement"><br> <!--子元素-->
						<child ref="childComponent"></child> <!-- 子组件-->
					</div>`
			})
			
			app.component('child', {
			 	data(){
			 		return {
			 			message: 'Java无难事'
			 		}
			 	},
				template: '<p>{{message}}</p>'
			})

			app.mount('#app');
		</script>
	</body>
</html>

  需要注意的是,refs属性只在组件渲染完成之后生效,并且它们都不是响应式的。要避免在模板和计算属性中访问refs。

2.4 provide和inject

root属性用于访问根实例,parent属性用于访问父组件实例,但如果组件嵌套的层级不确定,某个组件的数据或方法需要被后代组件所访问,又该如何实现呢?这时需要用到两个新的实例选项:provide和inject。provide选项允许指定要提供给后代组件的数据或方法,在后代组件中使用inject选项接收要添加到该实例中的特定属性。代码如下所示:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<div id="app">
			<parent></parent>
		</div>
		
		<script src="https://unpkg.com/vue@next"></script>
		<script>
		    const app = Vue.createApp({});
			app.component('parent', {
                data() {
                    return {
                        msg: 'Java无难事'
                    }  
                },
			    methods: {
			  	    sayHello(name){
			  		    console.log("Hello, " + name);
			  	    }
			    },
			    provide(){
			  	    return {
			  		    // 数据message和sayHello方法可供后代组件访问
			  		    message: this.msg,
			  		    hello : this.sayHello
			  	    }
			    },
				template: '<child/>',
			})
			
			app.component('child', {
				// 接收message数据属性和hello方法
			 	inject: ['message', 'hello'],
			 	mounted(){
			 		// 当自身的方法来访问
			 		this.hello('zhangsan');
			 	},
			 	// 当自身的数据属性来访问
				template: '<p>{{message}}</p>'
			})

			const vm = app.mount('#app')
		</script>
	</body>
</html>

  使用provideinject,父组件不需要知道哪些后代组件要使用它提供的属性,后代组件不需要知道被注入的属性来自哪里。
  不过上述代码也存在一些问题。首先注入的message属性并不是响应式的,当修改父组件的msg数据属性时,message属性并不会跟着改变。这是因为默认情况下,provide/inject绑定并不是响应式的,可以通过传递ref属性或reactive对象更改这个行为。(后面再说吧)
  其次,provice和inject将应用程序中的组件与它们当前的组织方式耦合起来,使得重构变得更加困难。
  如果数据需要在多个组件中访问,并且能够相应更新,可以考虑真正的状态管理解决方案–Vuex

回复

我来回复
  • 暂无回复内容