【vue】组件及组件化+组件⽣命周期

代码获取

04-组件及组件化+组件⽣命周期

⼀、组件及组件化

1. 为什么需要组件?

1.1 思考

以可折叠⾯板为例, 现要展⽰3个, 如何操作?

可折叠⾯板案例的代码 :

vue 复制代码
<script setup>
import { ref } from 'vue'
const visible = ref(false)
</script>
<template>
    <h3>可折叠⾯板</h3>
    <div class="panel">
        <div class="title">
            <h4>⾃由与爱情</h4>
            <span class="btn" @click="visible = !visible"> {{ visible ? '收起' : '展开' }} </span>
        </div>
        <div class="container" v-show="visible">
            <p>⽣命诚可贵,</p>
            <p>爱情价更⾼。</p>
            <p>若为⾃由故,</p>
            <p>两者皆可抛。</p>
        </div>
    </div>
</template>
<style lang="scss">
body {
    background-color: #ccc;
}

#app {
    width: 400px;
    margin: 20px auto;
    background-color: #fff;

    border: 4px solid green;
    border-radius: 1em;
    box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
    padding: 1em 2em 2em;
}

#app h3 {
    text-align: center;
}

.panel {
    .title {
        display: flex;
        justify-content: space-between;
        align-items: center;
        border: 1px solid #ccc;
        padding: 0 1em;
    }

    .title h4 {
        line-height: 2;
        margin: 0;
    }

    .container {
        border: 1px solid #ccc;
        padding: 0 1em;
        border-top-color: transparent;
    }

    .btn {
        cursor: pointer;
    }
}
</style>
1.2 解决⽅案

有请重量级主⻆ 组件 闪亮登场

  1. 把需要复⽤的⼀段标签, 抽离并封装到⼀个单独的vue⽂件⾥, 连同相关JS和CSS放到⼀起

  2. 哪⾥要⽤这个组件,哪⾥导⼊, 当做标签使⽤即可

1.2.1、新建⽂件并填充代码

新建 src/components/MyPanel.vue

vue 复制代码
<template>
    <div class="panel">
        <div class="title">
            <h4>⾃由与爱情</h4>
            <span class="btn" @click="visible = !visible"> {{ visible ? '收起' : '展开' }} </span>
        </div>
        <div class="container" v-show="visible">
            <p>⽣命诚可贵,</p>
            <p>爱情价更⾼。</p>
            <p>若为⾃由故,</p>
            <p>两者皆可抛。</p>
        </div>
    </div>
</template>

<script setup>
import { ref } from 'vue'
const visible = ref(false)
</script>

<style scoped>
.panel {
    .title {
        display: flex;
        justify-content: space-between;
        align-items: center;
        border: 1px solid #ccc;
        padding: 0 1em;
    }

    .title h4 {
        line-height: 2;
        margin: 0;
    }

    .container {
        border: 1px solid #ccc;
        padding: 0 1em;
        border-top-color: transparent;
    }

    .btn {
        cursor: pointer;
    }
}
</style>
1.2.2、App.vue导⼊并使⽤
vue 复制代码
<script setup>
// 导入
import MyPanel from './components/MyPanel.vue'
</script>
<template>
    <h3>可折叠⾯板</h3>
    <!-- 使用 -->
    <MyPanel />
    <MyPanel />
    <MyPanel />
</template>
<style lang="scss">
body {
    background-color: #ccc;
}

#app {
    width: 400px;
    margin: 20px auto;
    background-color: #fff;

    border: 4px solid green;
    border-radius: 1em;
    box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
    padding: 1em 2em 2em;
}

#app h3 {
    text-align: center;
}

</style>
1.3 总结
  1. 为什么需要组件?

​ 答:当遇到⼀段标签(UI)需要复⽤的时候

  1. 如何让这⼀段标签(UI)复⽤?

​ 答:抽离 -> 封装( JS+HTML+CSS ) -> 导⼊ -> 使⽤

2. 组件及组件化

2.1 组件

组件是⼀个 独⽴的、可复⽤ 的 Vue 实例,也是⼀段 独⽴的 UI 视图 ,代码上体现在是⼀个独⽴的.vue ⽂件,包含 JS+HTML+CSS 3 部分组成。

类似乐⾼和积⽊⼀样,我们可以通过任意的乐⾼或积分进⾏组合,拼装成我们需要的成品。

2.2 组件化
  • 定义:⼀种代码的开发思想,体现在⼀个⻚⾯可以拆分成⼀个个组件,每个组件有着⾃⼰独⽴的结构、样式、⾏为; 通过 组件的组合与拼装 形成⼀个完整的⻚⾯,本质是代码的⼀种拆分思想,化⼤为⼩、化繁为简、分⽽治之

  • 好处:各⾃独⽴、便于复⽤

2.3 总结
  1. 什么是组件

​ 答:⼀个 可复⽤的、独⽴的 Vue 实例(UI) ,包含 3 部分代码

  1. 组件化的好处是什么?

​ 答: 化⼤为⼩、化繁为简 , 利于代码复⽤和维护

3. 根组件 App.vue

3.1 根组件

整个应⽤最上层的组件,包裹所有普通⼩组件

3.2 组件是由三部分构成
  • 三部分构成

    • template:HTML 结构

    • script: JS 逻辑

    • style: CSS 样式 (可⽀持less/scss,需要装包)

  • 让组件⽀持less/scss

    • style标签, lang="less/scss" 开启 less/scss 功能

    • 装包: npm i less less-loader -D 或者 npm i sass -D

3.3 总结
  1. App组件我们称之为什么?

​ 答:根组件(顶层组件)

4. 组件的使⽤

4.1 创建

新建 .vue ⽂件,编写组件的 3 部分代码

4.2 导⼊
js 复制代码
import 组件对象 from '相对路径'
// eg
import MyPanel from './components/MyPanel.vue'
4.3 注册(仅限于全局组件)

注意: 局部组件⽆需注册,全局组件要在 main.js 中注册

4.4 使⽤

把组件当做⾃定义标签使⽤(单双标签均可)

js 复制代码
<组件名></组件名>
<组件名 />
    
// eg
<!-- ⼤驼峰标 双标签 -->
<MyPanel></MyPanel>
<!-- ⼤驼峰 ⾃闭合的单标签 -->
<MyPanel />
<!-- 烤串法 双标签 -->
<my-panel></my-panel>
<!-- 烤串法 ⾃闭合的单标签 -->
<my-panel />
4.5 练习

在App.vue中使⽤组件的⽅式完成下⾯布局

components/zxj2022Header.vue

vue 复制代码
<script setup></script>

<template>
    <div class="zxj-header">我是zxj-header</div>
</template>

<style>
.zxj-header {
    height: 100px;
    line-height: 100px;
    background-color: #8064a2;
}
</style>

components/zxj2022main.vue

vue 复制代码
<script setup></script>

<template>
    <div class="zxj-main">我是zxj-main</div>
</template>

<style>
.zxj-main {
    height: 400px;
    margin: 20px 0;
    line-height: 400px;
    background-color: #f79646;
}
</style>

components/zxj2022footer.vue

vue 复制代码
<script setup></script>

<template>
    <div class="zxj-footer">我是zxj-footer</div>
</template>

<style>
.zxj-footer {
    height: 100px;
    line-height: 100px;
    background-color: #4f81bd;
}
</style>

App.vue

vue 复制代码
<template>
    <div>
        <zxj2022Head />
        <zxj2022Main />
        <zxj2022footer />
    </div>
</template>

<script setup>
import zxj2022Head from './components/zxj2022Head.vue';
import zxj2022Main from './components/zxj2022Main.vue';
import zxj2022footer from './components/zxj2022footer.vue';
</script>

<style>
* {
    margin: 0;
}

#app {
    height: 100vh;
    padding: 10px;
    background: skyblue;
    font-size: 30px;
    color: #fff;
    text-align: center;
}
</style>
4.6 总结
  1. A组件内部导⼊组件能在B组件使⽤吗?

​ 答: no, 不能

  1. 使⽤组件⾃定义标签 时应该按照什么命名法?

​ 答: 1、⼤驼峰法

​ 2、烤串法

5. 组件的全局注册

5.1 特点

全局注册的组件,在项⽬的任何组件中都能使⽤

5.2 步骤
  1. 创建.vue组件(三个组成部分)

  2. main.js 中进⾏全局注册

5.3 使⽤⽅式

当成HTML标签直接使⽤:

  • 双标签: <组件名></组件名>

  • ⾃闭合的单标签: <组件名 />

5.4 注意

组件名规范: ⼤驼峰命名法或烤串法

5.5 语法
js 复制代码
// main.js
import MyPanel from './components/MyPanel.vue'
// 注册全局组件
// app.component('组件名', 组件对象)
// ⼤驼峰组件名
app.component('MyPanel', MyPanel)
// 烤串法组件名
app.component('my-panel', MyPanel)
5.6 总结
  1. 全局注册组件在任何⼀个组件中可不可以⽤?

​ 答:Yes,⼀旦注册,任意 .vue 中都可⽤

⼆、组件⽣命周期

1. ⽣命周期介绍

1.1 思考
  • 什么时候可以发送初始化渲染请求?(越早越好)
  • 什么时候可以开始操作DOM?(⾄少DOM得渲染出来)
1.2 概念

就是⼀个Vue实例(组件)从 创建 到 卸载 的整个过程

1.3 四个阶段

⽣命周期四个阶段:① 创建 ② 挂载 ③ 更新 ④ 卸载

  1. 创建阶段:创建响应式数据

  2. 挂载阶段:渲染模板

  3. 更新阶段:修改数据,更新视图

  4. 卸载阶段:卸载组件

1.4 总结
  1. 什么是vue的⽣命周期?

​ 答:组件从 创建到卸载 的过程

  1. 组件的⽣命周期共经历哪⼏个阶段?

​ 答:4个, 创建、挂载、更新、卸载/销毁

2. 组件⽣命周期钩⼦

2.1 介绍

每个 Vue 组件实例在创建时都需要经历⼀系列的初始化步骤,⽐如设置好数据监听,编译模板,挂载实例到真实 DOM 树上,以及在数据改变时更新 DOM。在此过程中,会 ⾃动运⾏⼀些函数 ,这些函数被称为【Vue⽣命周期钩⼦

2.2 作⽤

给了开发者在特定阶段添加⾃⼰代码的机会

2.3 代码⽰例(选项式API)

新建 components/LifeCycle.vue

vue 复制代码
<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')
    },

    unmounted() {
        console.log('8-mounted')
        // 关闭定时器
        clearInterval(this.timerId)
    }
}
</script>
<template>
    <div>
        <p>{{ count }}</p>
        <button @click="count++">+1</button>
    </div>
</template>
<style scoped></style>
2.4 总结
  1. ⽣命周期钩⼦函数的作⽤?

​ 答: 钩⼦函数在特定时机会 ⾃动执⾏ , 给了开发者在不同时机有 添加⾃⼰代码 的机会

  1. 选项式API下, 组件⾸次渲染,会执⾏哪⼏个钩⼦?

​ 答:5个, setup/beforeCreate/created/beforeMount/mounted

  1. 选项式API下, 如果⼀进⼊组件就向后端发请求,在哪个钩⼦进⾏?

​ 答: created

  1. 选项式API下, 最早可以操作原⽣DOM, 在哪个钩⼦进⾏?

​ 答:mounted

  1. 选项式API下, 组件销毁, 要做优化⼯作, 在哪进⾏?

​ 答: unmounted

3. 组合式API⽣命周期钩⼦

3.1 对⽐Vue2钩⼦函数

Vue2钩⼦函数(选项式) VS Vue3 钩⼦函数(组合式)

创建阶段 挂载阶段 更新阶段 销毁阶段
Vue2 beforeCreate created beforeMount mounted beforeUpdate updated beforeUnmount unmounted
Vue3 setup(⽹络请求) onBeforeMount onMounted(操作DOM) onBeforeUpdate onUpdated onBeforeUnmount onUnmounted(清理⼯作)
3.2 代码实例

components/LifeCycle.vue

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

<template>
<div>

</div>
</template>



<style scoped>

</style>
3.3 总结
  1. 组合式API下, 如果⼀进⼊组件就向后端发请求,在哪个钩⼦进⾏?

​ 答: setup

  1. 组合式API下, 最早可以操作原⽣DOM, 在哪个钩⼦进⾏?

​ 答: onMounted

  1. 组合式API下, 组件销毁, 要做优化⼯作, 在哪进⾏?

​ 答: onUnmounted

4. 案例-⽣命周期钩⼦应⽤

4.1 在setup中请求数据并渲染
4.1.1 静态代码
vue 复制代码
<script setup>
// 运⾏ vue3-node-server 项⽬, 进⼊根⽬录:
// 1. 安装依赖: npm i
// 2. 启动服务: npm run start
// 接⼝地址:http://localhost:4000/api/news
// 请求⽅式:get
// 请求参数:⽆
</script>
<template>
    <ul>
        <li class="news">
            <div class="left">
                <div class="title">5G商⽤在即,三⼤运营商营收持续下降</div>
                <div class="info">
                    <span>新京报经济新闻</span>
                    <span>2222-10-28 11:50:28</span>
                </div>
            </div>
            <div class="right">
                <img src="http://ajax-api.itheima.net/images/0.webp" alt="img" />
            </div>
        </li>
        <li class="news">
            <div class="left">
                <div class="title">5G商⽤在即,三⼤运营商营收持续下降</div>
                <div class="info">
                    <span>新京报经济新闻</span>
                    <span>2222-10-28 11:50:28</span>

                </div>
            </div>
            <div class="right">
                <img src="http://ajax-api.itheima.net/images/0.webp" alt="img" />
            </div>
        </li>
    </ul>
</template>

<style>
* {
    margin: 0;
    padding: 0;
}

ul {
    list-style: none;
}

.news {
    display: flex;
    height: 120px;
    width: 600px;
    margin: 0 auto;
    padding: 20px 0;
    cursor: pointer;
}

.news .left {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding-right: 10px;
}

.news .left .title {
    font-size: 20px;
}

.news .left .info {
    color: #999999;
}

.news .left .info span {
    margin-right: 20px;
}

.news .right {
    width: 160px;
    height: 120px;
}

.news .right img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
</style>
4.1.2 安装 axios
she 复制代码
npm i axios

# or

yarn add axios
4.1.3 请求数据
js 复制代码
import { ref, onMounted } from 'vue'
import axios from 'axios' // 引⼊ axios, 使⽤ axios 发送请求

const newsList = ref([])

axios.get('http://localhost:4000/api/news').then(res => {
    newsList.value = res.data.data
    console.log(newsList.value);
})
4.1.4 完整代码
vue 复制代码
<script setup>
// 运⾏ vue3-node-server 项⽬, 进⼊根⽬录:
// 1. 安装依赖: npm i
// 2. 启动服务: npm run start
// 接⼝地址:http://localhost:4000/api/news
// 请求⽅式:get
// 请求参数:⽆

import { ref, onMounted } from 'vue'
import axios from 'axios' // 引⼊ axios, 使⽤ axios 发送请求

const newsList = ref([])

axios.get('http://localhost:4000/api/news').then(res => {
    newsList.value = res.data.data
    console.log(newsList.value);
})


</script>
<template>
    <ul v-for="(news, index) in newsList" :key="news.id">
        <li class="news">
            <div class="left">
                <div class="title">{{ news.title }}</div>
                <div class="info">
                    <span>{{news.source}}</span>
                    <span>{{news.time}}</span>
                </div>
            </div>
            <div class="right">
                <img :src="news.img" alt="img" />
            </div>
        </li>
    </ul>
</template>

<style>
* {
    margin: 0;
    padding: 0;
}

ul {
    list-style: none;
}

.news {
    display: flex;
    height: 120px;
    width: 600px;
    margin: 0 auto;
    padding: 20px 0;
    cursor: pointer;
}

.news .left {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding-right: 10px;
}

.news .left .title {
    font-size: 20px;
}

.news .left .info {
    color: #999999;
}

.news .left .info span {
    margin-right: 20px;
}

.news .right {
    width: 160px;
    height: 120px;
}

.news .right img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
</style>

4.2 在onMounted中操作DOM

输⼊框聚焦

4.2.1 静态代码
vue 复制代码
<script setup>
</script>
<template>
    <div class="container">
        <img width="150"
            src="https://foruda.gitee.com/avatar/1698232656737798373/12025902_jie-c-language_1698232656.png"
            alt="logo" />
        <div class="search-box">
            <input type="text" id="search" />
            <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>
4.2.2 完整代码
vue 复制代码
<script setup>

// 操作 DOM, 在组件挂载的时候就是最佳时机
import { onMounted } from 'vue'
onMounted(() => {
    document.querySelector('#search').focus();
})

</script>
<template>
    <div class="container">
        <img width="150"
            src="https://foruda.gitee.com/avatar/1698232656737798373/12025902_jie-c-language_1698232656.png"
            alt="logo" />
        <div class="search-box">
            <input type="text" id="search" />
            <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>
相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax