夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础

  • 响应式

  • 组件

  • 过渡&动画

  • 可复用性

    • Mixin混入
    • 渲染函数
    • 全局API
    • Teleport
      • 与 Vue components 一起使用
      • 在同一目标上使用多个 teleport
      • 实战:无限循环分类列表
    • 组合式API
    • 引入css样式
    • 自定义hook
    • 自定义插件
    • 自定义事件
    • 自定义指令
  • 工具

  • 规模化

  • 路由(核心)

  • 组合式

  • Vuex

  • Pinia

  • 其他

  • Vue3 源码学习记录

  • 《Vue》笔记
  • 可复用性
夜猫子
2022-06-26
目录

Teleport

# Teleport

Dom 渲染控制,直接指定渲染位置无论被哪个标签包裹。

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

    <teleport to="body"> 
    /*Vue “将这里面的内容传送到‘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
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 与 Vue components 一起使用

如果 <teleport> 包含 Vue 组件,则它将始终是 <teleport> 父组件的逻辑子组件:

const app = Vue.createApp({
  template: `
    <h1>Root instance</h1>
    <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>
  `
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

在这种情况下,即使在不同的地方渲染 child-component,它仍将是 parent-component 的子级,并将从中接收 name prop。

这也意味着来自父组件的注入会正常工作,在 Vue Devtools 中你会看到子组件嵌套在父组件之下,而不是出现在他会被实际移动到的位置。

# 在同一目标上使用多个 teleport

一个常见的用例场景是一个可重用的 <Modal> 组件,它可能同时有多个实例处于活动状态。对于这种情况,多个 <teleport> 组件可以将其内容挂载到同一个目标元素。顺序将是一个简单的追加——稍后挂载将位于目标元素中较早的挂载之后。

<teleport to="#modals">
  <div>A</div>
</teleport>
<teleport to="#modals">
  <div>B</div>
</teleport>

<!-- result-->
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12

# 实战:无限循环分类列表

传统的属性结构列表递归时其样式会受到父组件的影响(会撑开父元素,导致父元素间留有大量空隙,不符合分类列表样式需求)。

可以使用 Teleport 使每一个循环列表与父组件同层。

一般的循环递归样式图:(点击父级后展开子级,但父级被撑开不在同一级) 在这里插入图片描述

一般分类列表的样式图:(点击父级后展开子级,父级样式不受影响,类似多级 tab 的效果)

image-20230424171840989

代码说明:

1.需要放置的位置

为了方便管理全局数据的统一性,采用单向数据流,基于 provide(注入) 实现定向数据来驱动循环组件的选中状态

<template>
  <div>
      <!-- 存放组件的容器 -->
     <div id="searchCategory"></div>
  </div>
  <!-- 循环分类组件(不用放在容器里,也尽量不要放在容器里) -->
  <categoryMenuVue :list="categoryData"></categoryMenuVue>
</template>

<script setup>
import { provide } from 'vue';
    
const categoryData = [ // 模拟数据
  {
    id: '01',
    name: '01',
    children: [
      { id: '11', name: '11' },
      { id: '12', name: '12' },
    ],
  },
  { id: '02', name: '02' },
  {
    id: '03',
    name: '03',
    children: [
      { id: '31', name: '31', children: [{ id: '41', name: '41' }] },
      { id: '32', name: '32' },
    ],
  },
];    
const categoryMenuIds = ref([]); // 每次分类被点击时,记录当前选中的分类id,如[03,31,41]
const categoryMenuIdChange = (lev, id) => { // 每次分类被点击时触发的回调,会返回被点击的分类的层级以及id
  categoryMenuIds.value.splice(lev, categoryMenuIds.value.length - 1, id); // 更新选中消息
  console.log(lev, id, categoryMenuIds.value);
};
provide('categoryMenu', { categoryMenuIdChange, categoryMenuIds });
    
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

2.递归组件

为了方便组件自调用,推荐将其注册为全局组件

// main.js
import { createApp } from 'vue';
import App from './App.vue';

import categoryMenuVue from "@/views/search/categoryMenu/index.vue"

app.component('categoryMenuVue', categoryMenuVue)
1
2
3
4
5
6
7

组件实现:

<!-- categoryMenuVue 组件 -->
<template>
  <Teleport to="#searchCategory"><!-- 关键所在,将每一次的循环组件都放到同级容器中去 -->
    <div class="content">
      <div class="title"><span v-if="list[0].parentId == '0'">所有分类:</span></div>
      <div class="list">
        <div v-for="(item, index) in list" :key="item.id">
          <a
            :class="{ active: item.id == categoryMenuIds[_level] }"
            @click="handleClickCategory(item, index)"
          >
            {{ item.name }}
          </a>
           <!-- 进行递归,组件自调用 -->
          <template v-if="item.children && item.children.length > 0 && item.id == searchOptions.id">
            <categoryMenuVue
              :list="item.children"
              :level="_level"
            ></categoryMenuVue>
          </template>
        </div>
      </div>
    </div>
  </Teleport>
</template>

<script setup>
import { reactive, computed, inject } from 'vue';

const { categoryMenuIdChange, categoryMenuIds } = inject('categoryMenu');

const props = defineProps(['list', 'level']);

const _level = computed(() => {
  if (props.level || props.level === 0) {
    return props.level + 1;
  } else {
    return 0;
  }
});

const searchOptions = reactive({
  id: '',
});

const handleClickCategory = (item) => {
  searchOptions.id = item.id;
  categoryMenuIdChange(_level.value, item.id);
};
</script>

<style lang="less" scoped>
a {
  color: black;
}
a:hover {
  color: #fb6e23;
}
.active {
  color: #fb6e23;
}
.content {
  display: flex;

  background-color: #f6f6f6;
  padding: 16px 24px;
  margin-bottom: 8px;
  .title {
    width: 80px;
    flex-basis: 80px;
    font-weight: 700;
  }
  .list {
    flex: 1;
    display: flex;
    flex-wrap: wrap;
    gap: 16px 24px;
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
编辑 (opens new window)
上次更新: 2023/7/11 18:57:41
全局API
组合式API

← 全局API 组合式API→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式