十、组件(7)

本章概要

  • 单文件组件
  • 组件通信的其它方式
    • 访问根实例
    • 访问父组件实例
    • 访问子组件实例或子元素
    • provide 和 inject

10.10 单文件组件

在很多 Vue 项目中,全局组件使用 app.component() 方法定义,然后使用 app.mount(‘#app’) 在页面内绑定一个容器元素。
这种方式在很多小规模的项目中运作的很好,在这些项目里 JavaScript 只是被用来加强特定的视图。然而,在更复杂的项目中,或者前端完全由JavaScript驱动时,以下缺点将变得非常明显:

  • 全局定义强制要求每个组件的命名不能重复
  • 字符串模板缺乏语法高亮显示,在 HTML 有多行的时候,需要用到反斜杠,或者 ES6 中的反引号“`”,而后者依赖于支持 ES6 的浏览器
  • 没有 CSS 的支持意味着当 HTML 和 JavaScript 被模块化为组件时,CSS 明显被遗漏了
  • 没有构建步骤,这限制为只能使用 HTML 和 ES5 JavaScript,而不能使用预处理器,如果 Pug一千的(Jade)和 Babel

在 Vue.js 中,可以使用单文件组件解决上述所有问题。在一个文件扩展名为 .vue 的文件中编写组件,可以将组件模板代码以 HTML 的方式书写,同时 JavaScript 与 CSS 代码也在同一个文件中编写。
例如:

<template>
    <div>
        <ul class="item">
            <li class="username">用户名:{{ post.user.username }},留言时间:{{ gstTime }}</li>
            <li class="title">主题:{{ post.title }},</li>
            <li>内容:{{ post.content }}</li>
        </ul>
    </div>
</template>
<style scoped>
    .item {
        border-top: solid 1px grey;
        padding: 15px;
        font-size: 14px;
        color: grey;
        line-height: 21px;
    }
    .username{
        font-size: 16px;
        font-weight: bold;
        line-height: 24px;
        color: #009a61;
    }
    .title {
        font-size: 16px;
        font-weight: bold;
        line-height: 24px;
        color: #009a61;
    }
    ul li {
        list-style: none;
    }
</style>
<script>
   export default{
    name:'postItem',
    data() {
        return {}
    },
    props:['post'],
    computed:{
        gstTime:function(){
            let d = new Date(this.post.gstTime);
            d.setHours(d.getHours - 8);
            return d.toLocaleString();
        }
    }
   }
</script>

在单文件组件中编写 CSS 样式规则时,可以添加一个 scoped 属性。该属性的作用是限定 CSS 样式只作用于当前组件元素,相当于是组件作用域的 CSS。

10.11 杂项

介绍组件开发中一些不常用但特殊需求下会用到的功能。

10.11.1 组件通信的其它方式

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

  • 父组件通过 prop 向子组件传递数据
  • 子组件通过自定义事件向父组件发起通知或进行数据传递
  • 子组件通过 slot 元素充当占位符,获取父组件分发的内容;也可以在子组件的 slot 元素上使用 v-bind 指令板顶一个插槽 prop ,向父组件提供数据

而此处将介绍组件通信的其它实现方式。

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 "葵花宝典";
				}
			}
		})

		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 窗口中的输出如下:

单价:188
总价:1880
葵花宝典

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

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 "重生之门";
				}
			},
			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>

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

3. 访问子组件实例或子元素

父组件要访问子组件实例或子元素,可以给子组件或子元素添加一个特殊的属性 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。

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>

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只小熊猫呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值