vue3学习 --- 组件补充和生命周期函数

342 阅读4分钟

动态组件

这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战

如果我们在页面上需要频繁通过v-if来判断需要渲染什么的组件的时候

如果条件过多,那么模板逻辑是十分繁琐的,此时vue提供了动态组件来简化对应的操作

动态组件是使用 component 组件,通过一个特殊的attribute is来实现

在动态组件上的传值和监听事件和普通组件是一模一样的

<template>
  <div>
    <button
      v-for="tab in tabs"
      :key="tab"
      :class="{ active: activeTab === tab }"
      @click="changeTab(tab)"
    >
      {{ tab }}
    </button>

    <!--
      动态组件 is的值是需要显示的组件名(是那些被注册过的组件名)
      注意: 这里is的值和组件名的使用规则是一致的
			所以即使activeTab的值为home,注册的组件名为Home,其依旧可以匹配上对应的组件
    -->
    <component :is="activeTab" />
  </div>
</template>

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

export default {
  name: 'App',

  components: {
    About,
    Home,
    Category
  },

  data: () => ({
    tabs: [
      'home',
      'about',
      'category'
    ],

    activeTab: 'home'
  }),

  methods: {
    changeTab(v) {
      this.activeTab = v
    }
  }
}
</script>

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

keep-alive

在因为默认情况下,我们在切换组件后,旧的组件会被销毁掉,再次回来时会重新创建组件

但是,在开发中某些情况我们希望继续保持组件的状态,而不是销毁掉

这个时候我们就可以使用一个内置组件: keep-alive

<keep-alive>
  <!-- 此时在组件切换的时候,旧的组件会被缓存下来 -->
  <component :is="activeTab" />
</keep-alive>

但是这么操作的时候,所有的组件都会被缓存下来,有的时候,我们只希望有部分的组件会被缓存下来

此时可以通过给keep-alive设置属性的方式来解决

属性取值说明
include`stringRegExpArray`只有名称匹配的组件会被缓存
exclude`stringRegExpArray`任何名称匹配的组件都不会被缓存
如果exclude的值和include的值冲突,exclude的优先级更高
max`numberstring`最多可以缓存多少组件实例,一旦达到这个数字,
那么缓存组件中最近没有被访问的实例会被销毁
<!--
	使用字符串的时候,多个组件之间使用逗号划分
	注意: 这里的值取的不是组件的名称,而是组件中的name选项的值(★★★)
-->
<keep-alive include="Home,Category">
	 <component :is="activeTab" />
</keep-alive>
<!-- 使用正则 -->
<keep-alive :include="/Home|Category/">
  <component :is="activeTab" />
</keep-alive>
<!-- 使用数组 -->
<keep-alive :include="['Home', 'Category']">
  <component :is="activeTab" />
</keep-alive>

异步组件

默认情况下,在构建整个组件树的过程中,因为组件和组件之间是通过模块化直接依赖的,那么webpack在打包时就会将组 件模块打包到一起

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

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

为此,Vue中给我们提供了一个函数:defineAsyncComponent

defineAsyncComponent接受两种类型的参数:

类型一: 工厂函数(用于创建组件的函数),该工厂函数需要返回一个Promise对象

<template>
  <div>
    <Home />
    <About />
  </div>
</template>

<script>
import { defineAsyncComponent } from 'vue'
// 异步加载对应的组件,直接在script中加载的目的,是为了可以在components中进行注册
// defineAsyncComponent 返回的结果就是我们所需要使用的组件 ---- 异步组件在进行打包的时候会被分包,形成对应的chunk_[hash值].js文件
const Home = defineAsyncComponent(() => import('./components/Home.vue'))
const About = defineAsyncComponent(() => import('./components/About.vue'))

export default {
  name: 'App',

  components: {
    Home,
    About
  }
}
</script>

类型二: 接受一个对象类型,对异步函数进行配置

const Home = defineAsyncComponent({
  loader: () => import('./components/Home.vue'),
  // 组件还没有被加载出来的时候显示的信息
  loadingComponent: Loader,
  // 组件加载错误的时候显示的信息
  errorComponent: Error,
  // 延迟2s后,再显示loadingComponent
  delay: 2000,
  // 单位ms,如果在timeout中所设置的时间之内组件还没有被下载下来
  // 无论是否报错,直接认定为失败
  // 默认值是 Infinity
  timeout: 0
})

但是,实际开发中,我们可能并不需要配置那么多的属性,为此vue提供了个内置组件suspense

Suspense是一个内置的全局组件,该组件有两个插槽

  • default:如果default可以显示,那么显示default的内容
  • fallback:如果default无法显示,那么会显示fallback插槽的内容
<suspense>
  <template #default>
     <About />
  </template>
  <!--
    如果默认插槽中的内容无法显示(error的时候)或者About组件还在加载的时候(loading的时候)
    会显示fallback插槽中的内容
  -->
  <template #fallback>
     <!--
        这里只是演示,所以也放置了异步组件,实际开发中,推荐在fallback插槽中放置同步组件
     -->
     <Home />
  </template>
</suspense>

$refs

某些情况下,我们在组件中想要直接获取到元素对象或者子组件实例,这个时候,我们可以给元素或者组件绑定一个ref的attribute属性

但是在Vue开发中我们是不推荐直接进行DOM操作的

$refs 是一个对象Object,持有注册过 ref attribute 的所有 DOM 元素和组件实例

<!-- 在refs注册对应的组件 -->
<About ref="about" />
// 取值
mounted() {
  console.log(this.$refs.title) // 原生元素获取的就是DOM对象
  console.log(this.$refs.about) // 组件获取的是组件实例对象(proxy对象)
  console.log(this.$refs.about.$el) // 如果需要获取组件的dom对象,需要取其属性$el的值
}

$parent$root

属性说明
$parent当前组件的父组件元素
$root根组件元素,一般指代的就是App组件

注意: 在Vue3中已经移除了**$children的属性**,所以不可以使用了

组件的v-model

我们在input中可以使用v-model来完成双向绑定 现在封装了一个组件,其他地方在使用这个组件时,在组件上也是支持v-model

做法类似于vue2中的.sync操作符

父组件

<template>
  <div>
    <Home v-model="msg" />
    <!-- 上面的写法等价于 -->
    <!-- 所以实际传递给子组件的是modelValue,不是msg -->
    <!-- 
				update:modelValue是vue中的一个自定义的事件
				子组件中更新的值,会直接作为事件参数被传递过来
				注意: $event直接获取到的就是对应的值,不需要在去取target的value属性 
		-->
    <Home :model-value="msg" @update:modelValue="msg = $event" />

    <h2>{{ msg }}</h2>
  </div>
</template>

<script>
import Home from './components/Home.vue'

export default {
  name: 'App',

  components: {
    Home
  },

  data() {
    return {
      msg: '123'
    }
  }
}
</script>

子组件

<template>
  <div>
    <input type="text" v-model="message">
  </div>
</template>

<script>
export default {
  name: 'Home',

  props: {
    // 实际传入的值是modelValue, 不是msg
    modelValue: String
  },

  emits: ['update:modelValue'],

  computed: {
    // 因为我们不应该直接修改props属性
    // 而且默认情况下,props是单向数据流,子组件修改props并不会影响父组件中对应的状态(引用类型状态除外)
    // 所以我们可以使用一个计算属性来完成对应的操作 --- 本质就是对update:modelValue进行对应的代理操作
    message: {
      get() {
        return this.modelValue
      },

      set(v) {
        this.$emit('update:modelValue', v)
      }
    }
  }
}
</script>

但是实际上我们在组件上可能需要多个属性都需要进行双向绑定,

此时就不可以单单使用v-model,而且传递过来的属性名不能都叫做modelValue

所以实际情况下,我们一般可以手动设置需要传递给子组件的props变量的名称

父组件

<template>
  <div>
    <!-- 使用msg替代原本默认的modelValue -->
    <!--
      此时如果需要设置多个双向绑定的值的时候
      <Home v-model:foo="foo" v-model:bar="bar" />
    -->
    <Home v-model:msg="msg" />

    <h2>{{ msg }}</h2>
  </div>
</template>

<script>
import Home from './components/Home.vue'

export default {
  name: 'App',

  components: {
    Home
  },

  data() {
    return {
      msg: '123'
    }
  }
}
</script>

子组件

<template>
  <div>
    <input type="text" v-model="message">
  </div>
</template>

<script>
export default {
  name: 'Home',

  props: {
    msg: String
  },

  emits: ['update:msg'],

  computed: {
    message: {
      get() {
        return this.msg
      },

      set(v) {
        this.$emit('update:msg', v)
      }
    }
  }
}
</script>

生命周期

每个组件都可能会经历从创建、挂载、更新、卸载等一系列的过程

在这个过程中的某一个阶段,用于可能会想要添加一些属于自己的代码逻辑

为此Vue给我们提供了组件的生命周期函数

生命周期函数

生命周期函数是一些钩子函数,在某个时间会被Vue源码内部进行回调

通过对生命周期函数的回调,我们可以知道目前组件正在经历什么阶段

那么我们就可以在该生命周期中编写属于自己的逻辑代码了

vue的生命周期钩子示意图

缓存组件的生命周期

对于缓存的组件来说,再次进入时,我们是不会执行created或者mounted等生命周期函数(第一次和最后一次除外)

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

这个时候我们可以使用activated 和 deactivated 这两个生命周期钩子函数来监听 --- 只有被keep-alive缓存了的组件才有这两个生命周期函数

activated() {
  console.log('activated')
},
deactivated() {
  console.log('deactivated')
}