《循序渐进Vue.js前端开发实战》电子书学习-第6章-组件进阶

在实际开发中,对组件进行简单的应用是暂时不够的,我们还需要理解组件渲染更加深层的原理,这有助于在开发中更加灵活的使用组件的功能

组件的生命周期与高级配置

组件在创建出来到渲染完成会经历一系列的过程,同样,销毁也是会有一系列的过程,组件从创建到销毁的这一系列也被称为组件的生命周期。在vue里,组件的生命周期的节点会被定义为一系列的方法,这种方法叫做生命周期钩子。有了这些生命周期方法,我们可以在合适的时机来完成合适的工作,例如在组件挂载前准备组件所需要的数据,当组件销毁时,清除某些残留的数据。

vue里也提供了许多对组件配置的高级api接口,包括对应用或组件进行全局配置的api功能接口以及组件内部相关的高级配置项

生命周期方法

我们可以先感受一下组件生命周期方法的调用时机

<!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>Document</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="Application">

  </div>
  <script>
    const root={
      beforeCreate() {
        console.log("组件创建前");
      },
      created() {
        console.log("组件创建完成");
      },
      beforeMount() {
        console.log("组件即将挂载前");
      },
      mounted() {
        console.log("组件挂载完成");
      },
      beforeUpdate() {
        console.log("组件即将更新前");
      },
      updated() {
        console.log("组件更新完成");
      },
      activated() {
        console.log("被缓存的组件激活时调用");
      },  
      deactivated() {
        console.log("被缓存的组件停用时调用");
      },
      beforeUnmount() {
        console.log("组件即将被卸载前调用");
      },
      unmounted() {
        console.log("组件被卸载后调用");
      },
      errorCaptured: (err, vm, info) => {
        console.log("捕获到来自子组件的异常时调用");
      },
      renderTracked({key, target, type}) {
        console.log("虚拟Dom重新渲染时调用");
      },
      renderTriggered({key, target, type}) {
        console.log("虚拟DOM被触发渲染时调用");
      },
    }
    const App=Vue.createApp(root)
    App.mount("#Application")
  </script>
</body>
</html>

每个方法都使用log标明其调用时机,输出

本次页面渲染只完成了四个组件的生命周期方法,这是因为我们使用的是vue根组件,页面渲染的过程中只执行了组件的创建和挂载过程,并没有完成更新和卸载的过程,如果某个组件是通过v-if指令来控制其渲染的,则其渲染状态进行切换的时候,组件会进行交替的挂载和卸载动作

<!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>Document</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="Application">
    <sub-com v-if="show"></sub-com>
    <button @click="changeShow"></button>
  </div>
  <script>
    const sub={
      beforeCreate() {
        console.log("组件创建前");
      },
      created() {
        console.log("组件创建完成");
      },
      beforeMount() {
        console.log("组件即将挂载前");
      },
      mounted() {
        console.log("组件挂载完成");
      },
      beforeUpdate() {
        console.log("组件即将更新前");
      },
      updated() {
        console.log("组件更新完成");
      },
      activated() {
        console.log("被缓存的组件激活时调用");
      },  
      deactivated() {
        console.log("被缓存的组件停用时调用");
      },
      beforeUnmount() {
        console.log("组件即将被卸载前调用");
      },
      unmounted() {
        console.log("组件被卸载后调用");
      },
      errorCaptured: (err, vm, info) => {
        console.log("捕获到来自子组件的异常时调用");
      },
      renderTracked({key, target, type}) {
        console.log("虚拟Dom重新渲染时调用");
      },
      renderTriggered({key, target, type}) {
        console.log("虚拟DOM被触发渲染时调用");
      },
    }
    const App=Vue.createApp({
      data() {
        return {
          show:false
        }
      },
      methods: {
        changeShow(){
          this.show=!this.show
        }
      },
    })
    App.component("sub-com",sub)
    App.mount("#Application")
  </script>
</body>
</html>

 

在上面列举的生命周期方法里,还有四个方法调用的比较多,即 renderTracked, renderTriggered,beforeupdate和update方法,当组件中的html元素发生渲染或者更新的时候,会调用这些方法,

<!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>Document</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="Application">
    <sub-com > 
      {{content}}
    </sub-com>
    <button @click="change">测试</button>
  </div>
  <script>
    const sub={
     
      beforeUpdate() {
        console.log("组件即将更新前");
      },
      updated() {
        console.log("组件更新完成");
      },
      renderTracked({key, target, type}) {
        console.log("虚拟Dom重新渲染时调用");
      },
      renderTriggered({key, target, type}) {
        console.log("虚拟DOM被触发渲染时调用");
      },
      template:`
      <div>
        <slot></slot>
        </div>
      `
    }
    const App=Vue.createApp({
      data() {
        return {
          content:0
        }
      },
      methods: {
        change(){
          this.content+=1
        }
      },
    })
    App.component("sub-com",sub)
    App.mount("#Application")
  </script>
</body>
</html>

结果:

这些生命周期钩子可以帮助我们在开发中更加有效的组织和管理数据

应用的全局配置选项

当调用了vue.createApp方法后,会创建一个vue应用实例,对于此应用实例,其内部封装了一个config对象,我们可以通过这个对象的一些全局选项来对其进行配置,常用的配置项有异常与警告捕获配置和全局属性配置

在vue应用运行时,会有异常和警告生成,我们可以使用自定义的函数对抛出的异常和警告进行处理,例如

App.config.errorHander=(err,vm,info)=>{
      //捕获运行中产生的异常
      //err参数是错误对象,info是具体的错误信息
    }
    App.config.warnHandle=(msg,vm,trace)=>{
      //捕获运行中产生的警告
      //msg是警告信息,trace是组件的关系回溯
    }

 之前使用组件,组件内部使用的数据要么是组件内部定义的,要么是通过外部属性从父组件传递进来的。在实际开发中,有些数据可能是全局的,例如应用名称,应用版本学习等,为了方便的在任意组件使用这些全局数据,可以通过globalProperties全局属性对象进行配置,例如

 App.config.globalProperties ={
      version:"1.0.0"
    } 

组件的注册方式

组件的注册分为全局注册和局部注册,直接使用应用实例的component方法注册的组件都是全局组件,即可以在应用内的任何地方使用这些组件,包括其他组件内部

<!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>Document</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="Application">
    <comp1></comp1>
  </div>
  <script>
    const App=Vue.createApp({})
    const comp1={
      template:`
      <div>
        组件1
        <comp2></comp2>
        </div>
      `
    }
    const comp2={
      template:`
      <div>
        组件2
        </div>
      `
    }
    App.component("comp1",comp1)
    App.component("comp2",comp2)
    App.mount("#Application")
  </script>
</body>
</html>

效果:

上面在comp1里面可以直接使用从comp2,全局注册组件很方便,但是很多时候不是最佳的编程方式。一个复杂的组件内部可能由很多的子组件所组成,这些子组件不需要暴露在父组件的外面,这时候如果运用全局注册,就会污染js代码,更优解是使用局部注册的方法

<!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>Document</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="Application">
    <comp1></comp1>
  </div>
  <script>
    const App=Vue.createApp({})
    const comp2={
      template:`
      <div>
        组件2
        </div>
      `
    }
    const comp1={
      components:{
        'comp2':comp2
      },
      template:`
      <div>
        组件1
        <comp2></comp2>
        </div>
      `
    }
    
    App.component("comp1",comp1)
    // App.component("comp2",comp2)
    App.mount("#Application")
  </script>
</body>
</html>

comp2组件只能在comp1里面来使用

组件props属性的高级用法

使用props可以方便的向组件传递数据,从功能上来说,props也可以称为组件的外部属性,通过props的不同传参,组件可以有很强的灵活性和扩展性

对prop属性进行验证

js是一种灵活、自由的编程语言,js中定义函数无需要指定参数的类型,这种编程的风格十分方便,但安全性有待考究。以vue组件为例,某个自定义组件需要使用props进行外部传值,如果其要接收的参数为一个数值,但是最终调用方法传输了一个字符串类型的数据,那组件内部会发生错误,vue在定义组件的props的时候,可以通过添加约束的方法来对其类型、默认值、是否选填进行配置

<!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>Document</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="Application">
    <comp1 :count="5"></comp1>
  </div>
  <script>
    const App=Vue.createApp({})
    const comp1={
      props:["count"],
      data() {
        return {
          thisCount:0
        }
      },
      methods: {
        click(){
          this.thisCount++
        }
      },
      computed:{
        innerCount:{
          get(){
            return this.thisCount+this.count
          }
        }
      },
      template:`
      <button @click="click">点击</button>
      <div>计数:{{innerCount}}</div>
      `
    }
    App.component("comp1",comp1)
    App.mount("#Application")
  </script>
</body>
</html>

上面代码定义了一个count的外部属性,这个属性在组件内是控制初始值的,在外部传递数值类型的数据到组件内部的时候必须用v-bind指令进行传递,直接使用HTML属性设置传递的方式会将传递的数据作为字符串而不是js表达式传递,例如 <comp1 count="5"></comp1>是不能正确渲染计数结果的

count属性的作用是作为组件内部计数的初始值,但是调用的方法不一定理解组件内部的逻辑,调用组件的时候极有可能会传递非数值类型的数据,例如

 <comp1 :count="{}"></comp1>

渲染是不正常的,我们可以在props进行约数来显示指定其类型。当组件的props配置为列表时,其表示定义的属性没有任何约束,如果将其配置为对象,就可以进行更多约束

  const comp1={
      props:{
        count:{
          type:Number,
          required:false,
          default:10
        }
      },

 在调用此组件的时候,如果属性不符合要求,会有警告信息

在实际开发里,建议所有的props都采用对象的方式进行定义,显式地设置其类型、默认值,这样不仅可以使组件调用更加安全,也侧面为开发者提供了组件的参数使用文档

如果指定属性的类型,不需要指定复杂的性质,可以直接用冒号来定义,如果是多种类型,可以用中括号定义

在对属性的默认值配置时,如果默认值获取复杂,也可以将其定义为函数,函数执行结果会被当中当前属性的默认值

vue中props的定义也支持进行自定义的验证,假设组件内需要接收的count属性的值需要大于10,可以通过自定义验证函数实现,如下

  props:{
        count:{
          validator:function(value){
            if(typeof(value)!='number'||value<=10){
              return false
            }
            return true
          }
        }
      },

props的只读性质

对组件内部,props是只读的,我们不能够在组件内部修改props属性的值,

props:{
        count:{
          validator:function(value){
            if(typeof(value)!='number'||value<=10){
              return false
            }
            return true
          }
        }
      },
      data() {
        return {
          thisCount:0
        }
      },
      methods: {
        click(){
          this.count++
        }
      },

当我们点击按钮的时候,页面的计数是没有改变的,并且控制台会抛出vue警告的信息

props的只读性能是vue单向数据流的一种体现,所有外部属性props都只允许父组件流动到子组件里去,子组件的数据是无法流向父组件的。因此,在组件内部修改props的值无效。以计数器为例,如果定义的props只是为了设置组件某些属性的初始值,可以使用计算属性进行桥接,也可以将外部属性的初始值映射到组件的内部属性上

 props:{
        count:{
          validator:function(value){
            if(typeof(value)!='number'||value<=10){
              return false
            }
            return true
          }
        }
      },
      data() {
        return {
          thisCount:this.count
        }
      },

组件数据注入

数据注入是一种便捷的组件间数据传递方式。父组件需要传递给子组件的时候,我们会使用props,但是当组件的嵌套层级很多,子组件需要使用多层之外的父组件的数据的时候,就非常麻烦,数据需要一层一层进行传递

<!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>Document</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="Application">
    <my-list :count="5"></my-list>
  </div>
  <script>
    const App=Vue.createApp({})
    const listCom={
      props:{
        count:Number
      },
      template:`
      <div style="border:red solid 10px;">
        <my-item v-for="i in this.count" :list-count="this.count" :index="i"></my-item>
        </div>
      `
    }
    const itemCom={
      props:{
        listCount:Number,
        index:Number
      },
      template:`
      <div style="border:blue solid 10px;"><my-label :list-count="this.listCount" :index="this.index"><my-label></div>
      `
    }
    const labelCom={
      props:{
        listCount:Number,
        index:Number
      },
      template:`
      <div>{{index}}/{{this.listCount}}</div>
      `
    }
    App.component("my-list",listCom)
    App.component("my-item",itemCom)
    App.component("my-label",labelCom)
    App.mount("#Application")
  </script>
</body>
</html>

上面的代码里面,定义了三个自定义组件,mylist用来创建一个视图列表,其中每一行的元素为myitem组件,myitem组件里又用mylabel组件进行文本显示,列表的每一行会渲染出当前行数和总行数。

繁琐的地方在于mylabel组件里需要使用mylist组件里面的count属性,要通过myitem组件里的数据才能顺利传递。随着嵌套变多,数据传递复杂得雅痞,我们可以使用数据注入的方式来跨层级

数据注入,指的是父组件可以向其子组件提供数据,不论在层级结构子组件多深。以上面为例,mylabe组件可以框muitem直接使用mylist组件里的数据

实现数据的注入需要使用组件的provide和inject两个配置项,提供数据的父组件需要设置provide配置项来提供数据,子组件需要设置inject配置项来获取数据,修改一下代码

<!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>Document</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="Application">
    <my-list :count="5"></my-list>
  </div>
  <script>
    const App=Vue.createApp({})
    const listCom={
      props:{
        count:Number
      },
      provide(){
        return {
          listCount:this.count
        }
      },
      template:`
      <div style="border:red solid 10px;">
        <my-item v-for="i in this.count" :index="i"></my-item>
        </div>
      `
    }
    const itemCom={
      props:{

        index:Number
      },
      template:`
      <div style="border:blue solid 10px;"><my-label :list-count="this.listCount" :index="this.index"><my-label></div>
      `
    }
    const labelCom={
      props:{
     
        index:Number
      },
      inject:['listCount'],
      template:`
      <div>{{index}}/{{this.listCount}}</div>
      `
    }
    App.component("my-list",listCom)
    App.component("my-item",itemCom)
    App.component("my-label",labelCom)
    App.mount("#Application")
  </script>
</body>
</html>

 运行代码,程序运行得很好,数据注入的方式传递,父组件不需要了解哪些子组件需要使用这些数据,子组件也不需要关系数据来自哪儿,这样子代码的可控性降低,所以需要视情况使用数据注入

组件mixin技术

组件开发的优势是提高代码的复用性,使用mixin技术,组件的复用性可以得到进一步的提高

使用mixin来定义组件

当开发大型前端项目的时候,可能会定义非常多的组件,这些组件可能一部分功能是通用的,对于这部分通用的功能,如果每个组件都编写一遍会非常麻烦,而且不易于以后的维护。

<!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>Document</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="Application">
    <my-com1 title="组件1"></my-com1>
    <my-com2 title="组件1"></my-com2>
    <my-com3 title="组件1"></my-com3>
  </div>
  <script>
    const App=Vue.createApp({})
    const com1={
      props:['title'],
      template:`
      <div style="border:red solid 2px">
        {{title}}
        </div>
      `
    }
    const com2={
      props:['title'],
      template:`
      <div style="border:blue solid 2px">
        {{title}}
        </div>
      `
    }
    const com3={
      props:['title'],
      template:`
      <div style="border:green solid 2px">
        {{title}}
        </div>
      `
    }
    App.component("my-com1",com1)
    App.component("my-com2",com2)
    App.component("my-com3",com3)
    App.mount("#Application")
  </script>
</body>
</html>

上面的代码定义的三个示例组件,每个组件都定义了一个名为title的外部属性,这部分代码是可以抽离出来作为独立的功能模块,需要此功能的组件只需要混入此功能模块即可

<!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>Document</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="Application">
    <my-com1 title="组件1"></my-com1>
    <my-com2 title="组件1"></my-com2>
    <my-com3 title="组件1"></my-com3>
  </div>
  <script>
    const App=Vue.createApp({})
    const myMixin={
      props:['title']
    }
    const com1={
     mixins:[myMixin],
      template:`
      <div style="border:red solid 2px">
        {{title}}
        </div>
      `
    }
    const com2={
      mixins:[myMixin],
      template:`
      <div style="border:blue solid 2px">
        {{title}}
        </div>
      `
    }
    const com3={
      mixins:[myMixin],
      template:`
      <div style="border:green solid 2px">
        {{title}}
        </div>
      `
    }
    App.component("my-com1",com1)
    App.component("my-com2",com2)
    App.component("my-com3",com3)
    App.mount("#Application")
  </script>
</body>
</html>

定义了一个混入对象,混入对象包含任意的组件定义选项,当此对象被混入组件时,组件会将混入对象里面的选项引入当前组件内部,类似于继承的语法

mixin选项的合并

当混入对象和组件定义了相同选项,vue可以只能的对这些选项进行合并,不冲突的配置将完整合并,冲突的配置会以组件里自己的配置为准

进行全局mixin

vue也支持对应用进行全局mixin混入,直接对应用实例进行mixin设置即可

app.mixin({

mounted(){}

})

但是这样会让之后注册的所有组件都混入这些选项,程序如果有问题,会增加排查问题的难度。全局mixin技术非常适合开发插件,如开发组件挂载的记录工具

使用自定义指令

vue中提供了自定义指令的能力,对于某些定制化的需求,配合自定义指令来封装组件可以让开发过程变得很简单

认识自定义指令

vue内置的指令已经提供了大部分核心功能,但是有时候仍然需要直接操作dom元素来实现业务功能,这时候就可以使用自定义指令

例如下面的例子,实现如下功能:页面提供一个input输入框,当页面被加载后,输入框默认处于焦点状态,用户可以直接对输入框进行输入

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="Application">
    <input type="text" v-getfocus />
  </div>
  <script>
    const App=Vue.createApp({})
    App.directive('getfocus',{
      mounted(element){
        console.log('组件获得了焦点');
        element.focus()
      }
    })
    App.mount("#Application")
  </script>
</body>
</html>

调用direvtive方法可以注册全局的自定义指令,getfocus是指令的名称,在使用的时候是和其他的自带指令一样要加v-的。

在自定义指令的时候,通常需要在组件内某些生命周期节点进行操作,自定义指令中除了支持mounted生命周期方法外,也支持使用beforemount,beforeupdate,updated,beforeunmount和unmounted生命周期方法,可以选择合适的时机来实现自定义指令的逻辑

上面的例子采用全局注册的方法来自定义指令,因此所有组件都可以用,如果只想让自定义指令在指令的组件上可用,可以采用局部注册,在组件内部进行directive配置来定义自定义指令,例子如下

 const sub={
      directives:{
        getfocus:{
          mounted(el){
            el.focus()
          }
        }
      },
      mounted(){
        console.log(this.version);
      }
    }
    App.component("sub-com",sub)

自定义指令的参数

vue内置的指令是可以设置值和参数的,如对于v-on指令,可以设置值为函数来响应交互事件,也可以通过设置参数来控制要监听的事件类型

自定义指令也可以设置值和参数,这些设置数据会通过一个param对象传递到指令中实现的生命周期方法中,例子如下

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="Application">
    <input type="text" v-getfocus:custom="1" />
  </div>
  <script>
    const App=Vue.createApp({})
 
    App.directive('getfocus',{
      mounted(element,param){
        if(param.value=="1"){
          element.focus()
        }
        console.log("参数"+param.arg);
        
      }
    })
    App.mount("#Application")
  </script>
</body>
</html>

指令设置的值1被绑定在param对象的value属性上,指令设置的custom参数被绑定在param对象的arg属性上了

有了参数,vue自定义指令的使用可以非常灵活,通过不同的参数进行区分,我们可以很方便的处理复杂的组件渲染逻辑

对于指令设置的值,其也允许直接设置为js对象,例如这样的设置也是合法的


    <input type="text" v-getfocus:custom="{a:1,b:3}" />

 使用组件的teleport功能开发全局弹窗

telepont是传说传递的意思,在v3提供的新功能。依赖teleport功能,在编写代码的时候,开发者可以将相关行为的逻辑和UI封装到同一个组件里去,以提高代码的聚合性

要明白teleport功能如何使用,以及适用的场景,我们可以通过下面的例子体会:全局弹窗组件,此组件自带一个触发按钮,当用户点击此按钮后,会弹出弹窗

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="Application">
   <my-alert></my-alert>
  </div>
  <script>
    const App=Vue.createApp({})
    App.component("my-alert",{
      template:`
      <div>
        <button @click="show=true">弹出弹窗</button>
        </div>
        <div v-if="show" style="text-align:center;padding:20px;position:absolute;top:45%;left:30%;width:40%;border:black solid 2px;back-ground-color:white">
          <h3>弹窗</h3>
          <button @click="show=false">隐藏弹窗</button>
          </div>
      `,
      data() {
        return {
          show:false
        }
      },
    })
    App.mount("#Application")
  </script>
</body>
</html>

定义了一个myalert的组件,组件默认提供了一个功能按钮,单击后会弹出弹窗,按钮和弹窗都被聚合在组件的内部

但是此组件的可用性并不好,当我们在其他组件内部使用此组件的时候,全局弹窗的布局可能就无法达到预期效果

  <div id="Application">
   <div style="position: absolute;width: 50px;">
    <my-alert></my-alert>
   </div>
  </div>

 

这个时候由于当前组件被放在了一个外部的div元素里面,因此其弹窗布局会受到非常大的影响

为了避免这种由于组件树结构的改变而影响组件内元素的布局的问题,一种方式是将触发事件的按钮和全局的弹窗分成两个组件编写,保证全局弹窗组件挂载在body标签的下面,但这样会让组件的逻辑被分散在不同的地方,不利于后期的维护,另一种方式是使用teleport

在定义组件的时候,如果组件的模板里的某些元素只能挂载在指定的标签霞,可以使用teleport来指定,可以理解为teleport是功能是将此部分元素传送到指令的标签下面,

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="Application">
   <div style="position: absolute;width: 50px;">
    <my-alert></my-alert>
   </div>
  </div>
  <script>
    const App=Vue.createApp({})
    App.component("my-alert",{
      template:`
      <div>
        <button @click="show=true">弹出弹窗</button>
        </div>
        <teleport to="body">
        <div v-if="show" style="text-align:center;padding:20px;position:absolute;top:45%;left:30%;width:40%;border:black solid 2px;back-ground-color:white">
          <h3>弹窗</h3>
          <button @click="show=false">隐藏弹窗</button>
          </div>
          </teleport>
      `,
      data() {
        return {
          show:false
        }
      },
    })
    App.mount("#Application")
  </script>
</body>
</html>

 优化后无论组件在哪儿都能正确局部

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值