十、组件(8)

本章概要

  • 递归组件
  • 异步更新队列
  • Teleport

10.11.2 递归组件

组件可以在自己的模板中递归调用自身,但这需要使用 name 选项为组件指定一个内部调用的名称。
当调用 Vue.createApp({}).component({})全局注册组件时,这个全局的 ID 会自动设置为该组件的name选项。
递归组件和程序语言中的递归函数调用一样,都需要有一个条件结束递归,否则就会导致无限循环。
例如,可以通过 v-if 指令(表达式计算为假时)结束递归。
以下是一个分类树状显示例子:

<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8">
</head>

<body>
	<div id="app">
		<category-component :list="categories"></category-component>
	</div>

	<script src="https://unpkg.com/vue@next"></script>
	<script>
		const CategoryComponent = {
			name: 'catComp',
			props: {
				list: {
					type: Array
				}
			},

			template: `
          			<ul>
          				<!-- 如果list为空,表示没有子分类了,结束递归 -->
          				<template v-if="list">
        						<li v-for="cat in list">
        							{{cat.name}}
        							<catComp :list="cat.children"/>
        						</li>
        					</template>
          			</ul>
          			`
		}
		const app = Vue.createApp({
			data() {
				return {
					categories: [
						{
							name: '程序设计',
							children: [
								{
									name: 'Java',
									children: [
										{ name: 'Java SE' },
										{ name: 'Java EE' }
									]
								},
								{
									name: 'C++'
								}
							]
						},
						{
							name: '前端框架',
							children: [
								{ name: 'Vue.js' },
								{ name: 'React' }
							]
						}]
				}
			},
			components: {
				CategoryComponent
			}
		}).mount('#app');
	</script>
</body>

</html>

渲染结果如下:
在这里插入图片描述

10.11.3 异步更新队列

代码如下:

<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8">
</head>

<body>
	<div id="app">
		<my-component></my-component>
	</div>

	<script src="https://unpkg.com/vue@next"></script>
	<script>
		const app = Vue.createApp({});
		app.component('my-component', {
			data() {
				return {
					message: '无限恐怖'
				}
			},
			methods: {
				change() {
					this.message = '无限曙光';
					console.log(this.$refs.msg.textContent);
				}
			},
			template: `
              	<div>
              		<p ref="msg">{{ message }}</p>
              		<button @click="change">修改内容</button>
              	</div>`
		})

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

</html>

代码很简单,当点击“修改内容”按钮时,修改组件 message 数据属性的值,然后在 Console 窗口中输出组件模板中 p 元素的文本内容。

按理说,p 元素的内容就是 message 属性的值,修改了 message 属性的值,在 change() 方法中理应输出修改后的值,但实际上输出的是“无限恐怖”。

这是因为 Vue 在数据变化需要更新 DOM 时并不是同步执行,而是异步执行的。每当侦听到数据更改时,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。

如果同一个观察者被多次触发,只会将其放入队列中一次。Vue在缓冲时会去除重复数据,这样可以避免不必要的计算 和 DOM 操作。

然后,在下一个时间循环tick中,Vue 刷新队列并执行实际的工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate ,如果执行环境不支持,则会采用 setTimeout(fn,0)代替。

对于本例,当在change() 方法中修改 message 属性值的时候,该组件不会立即重新渲染。当队列刷新时,组件会在下一个 tick 中更新。多数情况下,不需要关心这个过程,但是如果想在数据更改后立即访问更新后的 DOM ,这时就需要用到 Vue.nextTick(callback) 方法,传递给 Vue.nextTick() 方法的回调函数会在 DOM 更新后完成被调用。

修改上述代码,如下:

change() {
  this.message = '无限曙光';
  Vue.nextTick(() => console.log(this.$refs.msg.textContent))
}

使用浏览器在此访问页面,单击“修改内容”按钮,在 Console 窗口中的输出为 “无限曙光”。

除了使用全局的 Vue.nextTick() 方法外,在组件内部还可以使用实例的 nextTick() 方法,这样在回调函数中的 this 会自动绑定到当前组件实例上,而不是像上面的代码需要使用箭头函数来绑定 this 到组件实例。

change() {
  this.message = '无限曙光';
  this.$nextTick(function () {
    console.log(this.$refs.msg.textContent);
  })
}

10.11.4 Teleport

Vue 可以通过将 UI 和相关行为封装到组件中构建 UI ,组件之间可以嵌套,从而构成一个 UI 数。

然而,有事组件模板的一部分在逻辑上属于该组件,但从技术角度来看,应该将模板的这一部分移到 DOM 中的其它地方,位于 Vue 应用程序实例之外。

一个常见的场景是创建一个包含全屏模态的组件。在大多数情况下,模态的逻辑都存在于组件中的,但是我们会发现,模态的定位很难通过 CSS 来解决,我们不得不考虑对组件进行拆分。

Vue 3.0 官网给出了一个例子,有如下的 HTML 结构:

<body>
    <div id="app" style="position: relative;">
        <h3>Tooltips with Vue 3 Teleport</h3>
        <div>
            <modal-button></modal-button>
        </div>
    </div>
</body>

modal-button 组件在嵌套很深的 div 元素中渲染。modal-button 组件代码如下:

const app = Vue.createApp({});
  app.component('modal-button', {
    template: `
        <button @click="modalOpen = true">
            Open full screen modal! (With teleport!)
        </button>

        <div v-if="modalOpen" class="modal">
            <div>
                I am a modal !
                <button @click="modalOpen = false">
                    Close
                </button>
            </div>
        </div>
    `,
    data() {
        return { 
            modalOpen: false
        }
    }
})

运行结果如下:
在这里插入图片描述

modal-button 组件有一个 button 元素触发模态的打开,以及一个具有 .modal 样式类的 div 元素,它包含模态的内容和一个用于自我关闭的按钮。

.modal 样式类使用了一个样式表属性“position:absolute”,当 modal-button 组件在上面的 HTML 结构中渲染时,会发现由于模态在嵌套很深的 div 中渲染,样式属性 position:absolute 将相对于父级 div 元素应用。为了解决这个问题,Vue 3.0 给出了一个内置组件 teleport ,该组件允许控制在 DOM 中的哪个节点下渲染 HTML 片段。

teleport 组件有两个 prop ,如下:

  • to:字符串类型,必须的 prop 。其值必须是有效的查询选择器或 HTML 的元素名(如果在浏览器的环境中使用)。teleport 组件的内容将被移动到指定的目标元素中。
  • disabled:布尔类型,可选的 prop 。disabled 可以用于禁用 teleport 组件的功能,这意味着它的插槽内容将不会被移动到任何位置,而是在周围父组件中指定 teleport 的地方渲染。

修改 modal-button 组件的代码,使用 teleport 来告诉 Vue “将这个 HTML 传送到 body 标签下”。代码所示如下:

const app = Vue.createApp({});
app.component('modal-button', {
    template: `
        <button @click="modalOpen = true">
            Open full screen modal! (With teleport!)
        </button>

        <teleport to="body">
            <div v-if="modalOpen" class="modal">
                <div>
                    I'm a teleported modal! 
                    (My parent is "body")
                    <button @click="modalOpen = false">
                        Close
                    </button>
                </div>
            </div>
        </teleport>
    `,
    data() {
        return { 
            modalOpen: false
        }
    }
})

现在,当单机“Open full screen modal! (With teleport!)”按钮,Vue 会正确地将模态的内容在body标签下渲染。运行结果如下:
在这里插入图片描述

如果 teleport 的内容中包含了 Vue 组件,那么该组件在逻辑上仍是 teleport 父组件的子组件。代码如下:

const app = Vue.createApp({
    template: `
    <h1>Root instance</h1>
    <parent-component></parent-component>
    `
});
app.component('parent-component', {
    template: `
    <h2>This is a parent component</h2>
    <teleport to='#endofbody'>
        <child-component name="John"/>
    </teleport>
    `
})
app.component('child-component', {
    props:['name'],
    template: `
    <div>hello,{{ name }}</div>
    `
})
app.mount('#app');

不管 child-component 组件在什么位置渲染,它仍是 parent-component 组件的子组件,并且从父组件接收 name prop 。这意味着来自父组件的注入将按预期工作,并且子组件将嵌套在 Vue Devtools 中父组件之下,而不是放在实际内容移动到的位置。

一个常见的用例场景是一个可重用的 Modal 组件,其中可能同时有多个活动实例。对于这种情况,多个 teleport 组件可以将他们的内容挂载到同一个目标元素下。挂载顺序将是一个简单的追加,代码如下:

<teleport to="#modals">
    <div>A</div>
</teleport>
<teleport to="modals">
    <div>B</div>
</teleport>
<!-- 结果 -->
<div id="modals">
    <div>A</div>
    <div>B</div>
</div>
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一只小熊猫呀

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

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

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

打赏作者

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

抵扣说明:

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

余额充值