一、$refs的使用

某些情况下,我们在组件中想要直接获取到DOM元素对象或者子组件实例。

Vue开发中并不推荐进行向原生js一样操作DOM元素,此时可以给DOM元素或者组件绑定一个ref属性。

当前组件实例有一个$refs属性: 它是一个对象,对象中有注册过ref属性的DOM元素和组件实例。

1.1 代码示例

<template>
  <div class="app">
    <!-- 给h2、button中两个DOM元素绑定ref属性 -->
    <h2 ref="title" class="title" :style="{ color: titleColor }">{{ message }}</h2>
    <button ref="btn" @click="changeTitle">修改title</button>

    <!-- 给banner组件绑定ref属性 -->
    <banner ref="banner"/>
  </div>
</template>

<script>
  import Banner from "./Banner.vue"

  export default {
    components: {
      Banner
    },  
    data() {
      return {
        message: "Hello ref",
      }
    },
    methods: {
      changeTitle() {

        // 1.获取h2/button 这种DOM元素
        console.log(this.$refs.title)
        console.log(this.$refs.btn)

        // 2.获取banner组件: 组件实例
        console.log(this.$refs.banner)
        
        // 2.1.在父组件中可以主动的调用子组件的对象方法
        this.$refs.banner.bannerClick()

        // 2.2.获取banner组件实例, 获取banner中的DOM元素
        console.log(this.$refs.banner.$el)

        // 3.3.如果banner template是多个根节点, 拿到的是第一个node节点
        // 这种情况很少遇到,一般不会去给组件设置多个根节点
        console.log(this.$refs.banner.$el.nextElementSibling)

        // 4.组件实例还有两个属性:
        // 在Vue3中已经移除了$children的属性,不可以再使用了,这两个其实也不是很常用
        console.log(this.$parent) 	// 获取父组件
        console.log(this.$root) 	// 获取根组件 
      }
    }
  }
</script>

1.2 小结

  • template中给想要获取的元素添加ref属性。
  • Vue当前组件下,使用this.$refs去获取添加了ref属性的元素。

二、动态组件

假设有两个按钮,点击一下就切换一个组件。

一般来讲会有两种方案去实现这个需求:

  • 使用v-if或者v-show来显示不同的组件

  • 使用动态组件的方式

动态组件使用 component 组件,通过一个属性is 来实现组件切换

2.1 代码示例

<template>
  <div class="app">
    <div class="tabs">
      <template v-for="(item, index) in tabs" :key="item">
        <button :class="{ active: currentTab === item }" 
                @click="itemClick(item)">
          {{ item }}
        </button>
      </template>
    </div>
    <div class="view">

      <!-- 动态组件 component -->
      <!-- is中的组件需要来自两个地方: 1.全局注册的组件 2.局部注册的组件 -->
      <component name="张三" :age="18"
                 @homeClick="homeClick"
                 :is="currentTab">
      </component>
    </div>
  </div>
</template>

<script>
  import Home from './views/Home.vue'
  import About from './views/About.vue'
  import Category from './views/Category.vue'

  export default {
    components: {
      Home,
      About,
      Category
    },
    data() {
      return {
        tabs: ["home", "about", "category"],
        currentTab: "home"
      }
    },
    methods: {
      itemClick(tab) {
        this.currentTab = tab
      },
      homeClick(payload) {
        console.log("homeClick:", payload)
      }
    }
  }
</script>

<style scoped>
  .active {
    color: red;
  }
</style>

2.2 小结

  • 动态组件使用 component 组件,通过一个属性is 来实现组件切换
  • is中只需要传递组件的字符串名称。
  • 组件需要来自两个地方: 全局注册的组件或者局部注册的组件
  • 动态组件也可以给它们传值和监听事件,只要将属性和监听事件放到component上来使用
    • 其它没有这些属性数据或者事件的不用管就可以了

三、组件缓存

<KeepAlive> 是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。

简单的说,就是让一个组件保持存活状态。

默认情况下,一个组件实例在被替换掉后会被销毁,这会导致它丢失其中所有已变化的状态。

当这个组件再一次被显示时,会创建一个只带有初始状态的新实例。

而使用<KeepAlive>这个内置组件包裹需要保存状态的动态组件就可以解决上面的问题。

3.1 KeepAlive基本使用

<KeepAlive>
    <component :is="view" />
</KeepAlive>

3.2 KeepAlive的属性

  • include: 只有名称匹配的组件会被缓存

    • 数据类型支持:字符串、正则表达式、数组
    • 匹配首先检查组件自身的name选项
  • exclude: 任何名称匹配的组件都不会被缓存

    • 数据类型支持:字符串、正则表达式、数组
    • 匹配首先检查组件自身的name选项
  • max: 最大缓存实例数,达到最大值是缓存组件中最久没有被访问的缓存实例将被销毁

    • 数据类型支持:字符串、数字
<!-- 以英文逗号分隔的字符串 -->
<KeepAlive include="a,b">
  <component :is="view" />
</KeepAlive>

<!-- 正则表达式 (需使用 `v-bind`) -->
<KeepAlive :include="/a|b/">
  <component :is="view" />
</KeepAlive>

<!-- 数组 (需使用 `v-bind`) -->
<KeepAlive :include="['a', 'b']">
  <component :is="view" />
</KeepAlive>

<KeepAlive :max="10">
  <component :is="activeComponent" />
</KeepAlive>

3.3 缓存实例的生命周期

对于缓存的组件来说,再次进入时,不会执行created或者mounted等生命周期函数的。

但是有时候我们确实希望监听到何时重新进入到了组件,何时离开了组件。

这个时候可以使用activateddeactivated 这两个生命周期钩子函数来监听上述变化。

<script>
export default {
  activated() {
    // 在首次挂载、
    // 以及每次从缓存中被重新插入的时候调用
  },
  deactivated() {
    // 在从 DOM 上移除、进入缓存
    // 以及组件卸载时调用
  }
}
</script>

注意:

  • activated 在组件挂载时也会调用,并且 deactivated 在组件卸载时也会调用。
  • 这两个钩子不仅适用于 <KeepAlive> 缓存的根组件,也适用于缓存树中的后代组件。

四、异步组件

异步组件和打包是息息相关的。了解它对项目的优化也是很有必要的。

4.1 webpack分包

在这里插入图片描述

1)js文件目录解释

可以看到,之前的所有自己编写的Vue组件模块代码都被打包到了app.js中。

chunk-vendors字面意思是代码块供应商,其实就是项目中依赖得的各种第三包,也包括Vue的源代码。

.map文件,是为了方便调试用的。

因为打包后的代码已经无法直观的通过肉眼去看了。调试时,是不清楚到底哪行代码出现的问题。

.map文件就是一个把打包后的代码和源代码一一对应的映射文件,从而解决了上述问题。

2)这种代码方式带来的问题

随着项目的不断庞大,app.js文件的内容过大,会造成首屏的渲染速度变慢。

3)代码分包

打包时,对于一些不需要立即使用的组件,我们可以单独对它们进行拆分,拆分成一些小的代码块chunk.js

这些chunk.js会在需要时从服务器加载下来,并且运行代码,显示对应的内容。

这样就解决了app.js文件过大的问题。

具体实现:

import("./utils/math").then(res => {
   res.sum(20, 30)
})

import函数可以让webpack对导入文件进行分包处理。

这种方式是一种异步导入,返回的是一个promise对象。
在这里插入图片描述

可以看到多了两个以749开头的文件。这就是import函数导入的异步组件,webpack会对它进行分包处理。

4.2 Vue中异步组件

在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。

目的是可以对其进行分包处理。Vue 提供了defineAsyncComponent方法来实现此功能。

defineAsyncComponent接受两种类型的参数:

  • 工厂函数:该工厂函数需要返回一个Promise对象
  • 对象类型:对异步函数进行配置
1)工厂函数

ES模块动态导入会返回一个Promise,所以多数情况下我们会将它和 defineAsyncComponent 搭配使用。

类似 ViteWebpack 这样的构建工具也支持此语法 ,并且会将它们作为打包时的代码分割点。

<script>
    import { defineAsyncComponent } from 'vue'

    const AsyncComp = defineAsyncComponent(() =>
      import('./components/MyComponent.vue')
    )
    
    export default {
        components: {
			AsyncComp
        },
    }
</script>
2)对象类型
<script>
    const AsyncComp = defineAsyncComponent({
        // 加载函数
        loader: () => import('./Foo.vue'),

        // 加载异步组件时使用的组件
        loadingComponent: LoadingComponent,
        // 展示加载组件前的延迟时间,默认为 200ms
        delay: 200,

        // 加载失败后展示的组件
        errorComponent: ErrorComponent,
        // 如果提供了一个 timeout 时间限制,并超时了
        // 也会显示这里配置的报错组件,默认值是:Infinity
        timeout: 3000
    })
    
    export default {
        components: {
			AsyncComp
        },
    }
</script>

这种写法实际用的比较少,了解即可。一般都会用路由懒加载的方式。

3)注意点
<script>
  const Category = import("./views/Category.vue")
  export default {
    components: {
      Category: AsyncCategory
    }
  }
</script>

可能有人会有疑问,为什么.vue文件中不用import函数进行导入。

因为import函数进行导入返回的是一个Promise对象。Promise对象在components中是无法使用的。

五、组件混入

使用组件化的方式在开发整个Vue的应用程序,但是组件和组件之间有时候会存在相同的代码逻辑。

如果想要对相同的代码逻辑进行抽取,在Vue2Vue3中都支持的一种方式就是使用Mixin的方式来完成。

5.1 Mixin的作用如下:

  • Mixin提供了一种非常灵活的方式,来分发Vue组件中的可复用功能

  • 一个Mixin对象可以包含任何组件选项

  • 当组件使用Mixin对象时,所有Mixin对象的选项将被 混合 进入该组件本身的选项中

5.2 局部混入代码示例

1)新建mixin.js存放想要抽取的代码逻辑
export default {
  data() {
    return {
      message: "Hello World"
    }
  },
  created() {
    console.log("message:", this.message)
  }
}
2)组件中混入mixin.js中的代码逻辑
<template>
  <h2>组件A</h2>
</template>

<script>
  // 引入抽取出来的代码逻辑
  import messageMixin from '../mixins/mixin'

  export default {
    // 对messageMixin中的代码逻辑进行混入
    mixins: [messageMixin]
  }
</script>

<style scoped>
</style>

A组件混入mixin.js中的代码后,也就获得了mixin.js里面封装的功能。

5.3 全局混入Mixin代码示例

main.js中使用app对象的mixin方法对所有组件进行代码混入。

const app = createApp(App)
// 这里面就是需要混入所有组件的内容
app.mixin({
  created() {
    console.log("mixin created")
  }
})
app.mount('#app')

全局混入用的并不多,了解即可

5.4 Mixin对象中的选项和组件对象中的选项发生冲突的解决方式

  • data函数的返回值对象冲突

    • 返回值对象默认情况下会进行合并

    • data返回值对象的属性发生了冲突,那么会保留组件自身的数据

  • 如何生命周期钩子函数冲突

    • 生命周期的钩子函数会被合并到数组中,都会被调用
  • 其它值为对象的选项,如 methodscomponentsdirectives,将被合并为同一个对象。

    • 比如都有methods选项,并且都定义了方法,那么它们都会生效
    • 但是如果对象的key相同,那么会取组件对象本身的键值对
Logo

前往低代码交流专区

更多推荐