【Vue】组件生命周期 && 组件生命周期钩子

文章目录

一、生命周期介绍

一个 Vue 实例(组件)从创建到卸载的整个过程,称为其生命周期。从宏观角度来看,一共有四个阶段,如下所示:

  1. 创建 :初始化 propsdatamethods 等响应式数据。
  2. 挂载 :渲染模板 template,把虚拟 DOM 渲染成真实 DOM,显示在页面上。
  3. 更新:当数据变化时,重新渲染视图。
  4. 卸载:组件销毁、释放资源。

二、组件生命周期钩子

每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据监听,编译模板,挂载实例到真实 DOM 树上,以及在数据改变时更新 DOM

在上述过程中,会自动运行一些函数,这些函数被称为【Vue生命周期钩子】

作用:钩子函数在特定时机会自动执行,这给了开发者在特定阶段添加自己代码的机会。

选项式 API 的代码示例

新建 components/LifeCycle.vue文件:

javascript 复制代码
<script>
export default {
  // 提供响应式数据
  data() {
    return {
      count: 0
    }
  },
  // 提供方法/函数
  methods: {
    fn() {
      console.log('fn 函数执行了')
    }
  },
  
  setup() {
    console.log('0-setup')
  },
  
  // 创建阶段(第一阶段):Vue组件创建/出生阶段:
  // 创建前:此时无法访问 data 数据,也无法调用 methods 方法
  beforeCreate() {
    console.log('1-beforeCreate')
    console.log(this.count) // undefined
    console.log(this.fn) // undefined
  },
  // 创建后:此时可以访问 data 数据,也可以调用 methods 方法
  created() {
    console.log('2-created')
    console.log(this.count) // 0
    console.log(this.fn) // 访问到函数
    this.fn()
    
    // 开启定时器
    // 给当前组件实例新增了一个 timerId 属性,保存了当前定时器的 id 值
    this.timerId = setInterval(() => {
      console.log(this.count)
    }, 1000)
  },
  
  // 挂载阶段(第二阶段):模版渲染阶段
  // 挂载前:此时写在 template 下的标签还没有变成真实DOM,故而无法获取DOM
  beforeMount() {
    console.log('3-beforeMount')
    console.log(document.querySelector('p')) // null
  },
  // 挂载后:此时写在 template 下的标签已经变成了真实DOM,故而可以获取DOM(是最早可以操作DOM的时机)
  mounted() {
    console.log('4-mounted')
    console.log(document.querySelector('p'))// <p>0</p>
    document.querySelector('p').style.color = 'red'
  },
  
  // 更新阶段(第三阶段):数据变了,组件重新渲染的过程
  // 更新前
  beforeUpdate() {
    console.log('5-beforeUpdate')
    // console.log(this.count)
    console.log(document.querySelector('p').innerText)// 旧内容(以前的内容)
  },
  // 更新后
  updated() {
    console.log('6-updated')
    // console.log(this.count)
    console.log(document.querySelector('p').innerText)// 新内容
  },
  
  // 卸载阶段(第四阶段):组件移除阶段
  beforeUnmount() {
    console.log('7-beforeUnmount')
    
    // 用于 依赖 DOM 或组件存在的清理逻辑,比如要解除某个挂在组件 DOM 上的事件监听。
  },
  unmounted() {
    console.log('8-mounted')
    
    // 这里尝试访问 this.$el 或 document.querySelector('#xxx')
    // 已经拿不到了,因为 DOM 被销毁了
    // 但是可以关闭定时器之类非 DOM 的资源
    clearInterval(this.timerId)
  }
}
</script>

<template>
  <div>
    <p>{{ count }}</p>
    <button @click="count++">+1</button>
  </div>
</template>

App.vue文件:

javascript 复制代码
<script setup>
    import { ref } from 'vue'
    import LifeCycle from './components/LifeCycle.vue'
    
    const isAlive = ref(true)
</script>

<template>
    <life-cycle v-if="isAlive"/>
</template>

三、组合式API生命周期钩子

1. 与 Vue2 钩子函数的对比

阶段 Vue2(选项式) Vue3(组合式)
创建阶段 beforeCreate、created setup (网络请求)
挂载阶段 beforeMount、mounted onBeforeMount、onMounted (操作DOM)
更新阶段 beforeUpdate、updated onBeforeUpdate、onUpdated
销毁阶段 beforeUnmount、unmounted onBeforeUnmount、onUnmounted (清理工作)

其中 vue3<script setup> 就直接包括了 vue2 中的 setup()beforeCreate()created(),所以可以直接在 <script setup> 中进行网络请求

2. 代码实例

javascript 复制代码
<script setup>
    import { onMounted, onUnmounted } from 'vue'
    
    // 开启定时器
    const timer = setInterval(() => {
      console.log('Hello World')
    }, 1000)
    
    // 组件挂载后
    onMounted(() => {
      // console.log(document.querySelector('p'))
      document.querySelector('p').style.color = 'green'
    })
    
    // 组件卸载后
    onUnmounted(() => {
      // 关闭定时器
      clearInterval(timer)
    })
</script>

四、案例-生命周期钩子应用

1. 在 setup 中发起网络请求并渲染数据

① 安装axios

bash 复制代码
npm i axios

② 完整代码

javascript 复制代码
<script setup>
    // 安装 axios
    // 导入模块
    // 定义请求函数并调用
    import axios from 'axios'
    import { ref } from 'vue'
    const i = ref(0)

    // 图片列表
    const images = ref([])

    getBannerData()

    async function getBannerData() {
        // 发请求,调接口
        const resp = await axios({
            method: 'GET', // 请求方式
            url: 'http://localhost:4000/api/banner' // 请求路径
        })
        // 保存数据
        console.log(resp);
        
        images.value = resp.data.data
    }
    const prev = () => {
        i.value--
        if (i.value <= -1) {
            i.value = images.value.length - 1
        }
    }

    const next = () => {
        i.value++
        if (i.value >= images.value.length) {
            i.value = 0
        }
    }

    let timer = null

    const play = () => {
        timer = setInterval(() => {
            next()
        }, 3000)
    }

    play()

    const stop = () => {
        clearInterval(timer)
    }
</script>

<template>
    <div
        class="banner"
        @mouseenter="stop"
        @mouseleave="play">
        <ul>
            <li
                v-for="(url, index) in images"
                :class="{ active: index === i }">
                <img :src="url" />
            </li>
        </ul>
        <div class="indicator">
            <span
                v-for="(n, index) in images.length"
                :key="n"
                :class="{ active: index === i }"
                @click="i = index">
            </span>
        </div>
        <div class="ctrl">
            <a
                href="javascript:;"
                class="btn prev"
                @click="prev"
                >&lt;</a
            ><a
                href="javascript:;"
                class="btn next"
                @click="next"
                >&gt;</a
            >
        </div>
    </div>
</template>

<style>
    * {
        margin: 0;
        padding: 0;
    }
    a {
        text-decoration: none;
        color: #000;
    }

    .banner {
        position: relative;
        width: 1200px;
        height: 337px;
        margin: 150px auto;
    }
    .banner ul {
        width: 100%;
        height: 100%;
        list-style: none;
    }

    .banner ul li {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        opacity: 0;
        transition: opacity 1s;
    }
    .banner ul li.active {
        opacity: 1;
    }

    .banner .indicator {
        display: flex;
        justify-content: center;
        position: absolute;
        left: 0;
        bottom: 20px;
        width: 100%;
    }

    .banner .indicator span {
        width: 30px;
        height: 2px;
        margin: 0 5px;
        cursor: pointer;
        background: rgba(255, 2555, 255, 0.6);
    }
    .banner .indicator span.active {
        background: rgba(255, 255, 255, 1);
    }
    .btn {
        position: absolute;
        top: 50%;
        width: 30px;
        height: 30px;
        line-height: 30px;
        margin-top: -15px;
        font-size: 14px;
        text-align: center;
        background: rgba(0, 0, 0, 0.3);
        color: #fff;
        border-radius: 50%;
    }
    .btn.prev {
        left: 15px;
    }
    .btn.next {
        right: 15px;
    }
</style>

2. 在 onMounted 中操作 DOM

目标:打开网页的时候,输入框自动聚焦

javascript 复制代码
<script setup>
    import { onMounted } from 'vue'
    
    // 组件挂载后
    onMounted(() => {
      // 获取 input 元素
      const input = document.querySelector('input')
      // 调用 focus 聚焦
      input.focus()
    })
</script>

<template>
  <div class="container">
    <img 
      width="150" 
      src="https://th.bing.com/th/id/ODL.ce819d0be740fd3c6b5e42d538119fab?w=310&h=198&c=7&rs=1&bgcl=ffff14&r=0&o=6&dpr=1.3&pid=AlgoBlockDebug" 
      alt="logo" />
    <div class="search-box">
      <input type="text" />
      <button>搜 索</button>
    </div>
  </div>
</template>

<style>
html,
body {
  height: 100vh;
}
.container {
  position: absolute;
  top: 30%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
}
.container .search-box {
  display: flex;
}
.container img {
  margin-bottom: 30px;
}
.container .search-box input {
  width: 512px;
  height: 17px;
  padding: 12px 16px;
  font-size: 16px;
  margin: 0;
  vertical-align: top;
  outline: 0;
  box-shadow: none;
  border-radius: 10px 0 0 10px;
  border: 2px solid #c4c7ce;
  background: #fff;
  color: #222;
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
}
.container .search-box button {
  width: 112px;
  height: 44px;
  line-height: 42px;
  background-color: #ad2a27;
  border-radius: 0 5px 5px 0;
  font-size: 17px;
  box-shadow: none;
  font-weight: 400;
  margin-left: -1px;
  border: 0;
  outline: 0;
  letter-spacing: normal;
  color: white;
  cursor: pointer;
}
body {
  background: #f1f2f3 no-repeat center / cover;
}
</style>
相关推荐
建群新人小猿3 小时前
陀螺匠企业助手—个人简历
android·大数据·开发语言·前端·数据库
CHU7290353 小时前
在线教学课堂APP前端功能:搭建高效线上教学生态
前端·人工智能·小程序·php
be or not to be4 小时前
JavaScript 对象与原型
开发语言·javascript·ecmascript
前端 贾公子4 小时前
Git优雅使用:git tag操作
javascript·github
We་ct5 小时前
LeetCode 125. 验证回文串:双指针解法全解析与优化
前端·算法·leetcode·typescript
帅得不敢出门5 小时前
Android Framework在mk中新增类似PRODUCT_MODEL的变量并传递给buildinfo.sh及prop属性中
android·linux·前端
她超甜i6 小时前
css省略号展示,兼容性强,js判断几行,不需要定位
javascript·css·vue.js
小码吃趴菜6 小时前
【无标题】
前端·chrome
毕设源码-朱学姐7 小时前
【开题答辩全过程】以 基于HTML5的购物网站的设计与实现为例,包含答辩的问题和答案
前端·html·html5