likes
comments
collection
share

Teleport 传送门及render函数

作者站长头像
站长
· 阅读数 15

Teleport 传送门

功能

将组件或者组件中的内容挂载到其他DOM的位置上

实现一个需求

给绝对定位的元素加上覆盖整个页面的蒙层mask,由于绝对定位的元素只能相对于其父元素,如果父元素的面积很小,就做不到全屏的mask,因此需要将这个绝对定位的元素挂到body或者其他div上

1. 使用Teleport 传送门之前

敲代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Teleport 传送门</title>
    <style>
        .area {
            position: absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%,-50%);
            width: 200px;
            height: 300px;
            background: green;
        }
        .mask{
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, .5);
        }
    </style>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="root"></div>
    <script>
        const app = Vue.createApp({
            data() {
                return {
                    show: false
                }
            },
            methods:{
                handleBtnClick(){
                    this.show = !this.show
                }
            },
            template: `
                <div class="area">
                  <button @click="handleBtnClick">按钮</button>
                  <div class="mask" v-show="show"></div>
                </div>
            `
        })
        const vm = app.mount('#root')
    </script>
</body>

</html>

运行结果

Teleport 传送门及render函数

结果:由于绝对定位的mask的父元素为绿色背景的area,mask中的样式top left right bottom只能作用于父元素上,因此mask的范围就只能限定于它的父元素area中,但是实际是想让mask作用于整个页面

因此如果mask可以放到body中或者其他div上就好了

难道要操作DOM吗?

不用!Vue为我们提供了Teleport 传送门功能

2. 使用Teleport 传送门之后

敲代码

// 相同代码已省略

const app = Vue.createApp({
    data() {
        return {
            show: false
        }
    },
    methods:{
        handleBtnClick(){
            this.show = !this.show
        }
    },
    template: `
        <div class="area">
          <button @click="handleBtnClick">按钮</button>
          <teleport to="body">
            <div class="mask" v-show="show"></div>
          </teloport>
        </div>
    `
})
const vm = app.mount('#root')

运行结果

Teleport 传送门及render函数

Teleport 传送门及render函数

我们可以发现,mask已经放到body中了

备注

<div class="area">
  <button @click="handleBtnClick">按钮</button>
  <teleport to="body">
    <div class="mask" v-show="show"></div>
  </teloport>
</div>
  • 这段代码的意思是渲染的时候就不放在class为area的标签里,而是传送到body标签中

  • 当然不仅仅可以传到body中,可以传到任何的div中,如:

<teleport to="#wrapper">
    <div class="mask" v-show="show"></div>
</teloport>
  • 传送门除了可以将页面中的元素放到指定的DOM元素中外,其他的逻辑和写组件是一样的,作用域还是在当前的组件中

render函数

敲代码

1. 让插槽中的字重为h1

 const app = Vue.createApp({
    template: `
        <my-title>
          Hello World!
        </my-title>
    `
})

app.component('my-title', {
    template: `
      <h1>
          <slot />
      </h1>
    `
})

const vm = app.mount('#root')

运行结果

Teleport 传送门及render函数

2. 如何让字重受到外部参数的控制?

给子组件传值?

敲代码

const app = Vue.createApp({
    template: `
        <my-title :level="2">
            Hello World!
        </my-title>
    `
})

app.component('my-title', {
    props:['level'],
    template: `
      <h1 v-if="level === 1"><slot /></h1>
      <h2 v-if="level === 2"><slot /></h2>
      <h3 v-if="level === 3"><slot /></h3>
    `
})

const vm = app.mount('#root')

通过这种方式展示不同自重,代码显得不够优雅

3. 子组件中使用vue提供的render函数替代模板template

敲代码

const app = Vue.createApp({
    template: `
        <my-title :level="1">
            Hello World!
        </my-title>
    `
})

app.component('my-title', {
    props:['level'],
    render(){
        const { h } = Vue
        return h('h' + this.level,{},this.$slots.default())
    }
})


const vm = app.mount('#root')

备注

  • this.$slots.default():表示使用插槽中的内容

3.render函数和模板template之间存在的关系是什么?

虚拟DOM:

// 真实DOM
<div>hello</div>

// render函数
return h('div',{},'hello')

// 虚拟DOM: 通过JS对象表述这个div标签
{
  tagName: 'div',
  attributes: {},
  text: 'hello',
}

再来看下例子中render函数的返回值(即虚拟DOM)

 return h('h' + this.level,{},this.$slots.default())

备注

  • 第一个参数'h' + this.level对应的就是tagName
  • 第二个参数对应attributes,即标签上的属性,如<div a=1>hello</div>,如果有就是{a:1}否则就是空对象{}
  • 第三个参数this.$slots.default()对应的就是虚拟DOM中标签的文本内容text

Vue里面把template变成render函数,render函数再去返回一个虚拟DOM的好处

  • 可以让Vue的性能更快
  • 可以让Vue具备跨平台的能力

备注

1.template ==> render函数(被解析成) ==> 调用h函数 ==> h函数生成虚拟DOM(JS对象) ==> (映射成)真实DOM ==> 展示到页面上

2.上面例子就是跳过将template转成render函数,而是直接写render函数

h函数的第三个参数还可以是一个数组,可以继续嵌套虚拟DOM

敲代码

  render(){
    const { h } = Vue
    return h('h' + this.level,{},[
        this.$slots.default(),
        h('h3',{},'我是嵌套在h1标签中的h3标签中的内容')
    ])
}

运行结果

Teleport 传送门及render函数

当然每一个h函数的第三个参数都可以写成数组的形式,生成无限嵌套的JS对象,从而生成无限嵌套的虚拟DOM,最终生成嵌套的DOM节点展示到页面上