飞道的博客

十、组件(8)

315人阅读  评论(0)

本章概要

  • 递归组件
  • 异步更新队列
  • 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>

转载:https://blog.csdn.net/GXL_1012/article/details/127834310
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场