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 解决⽅案
有请重量级主⻆ 组件 闪亮登场
-
把需要复⽤的⼀段标签, 抽离并封装到⼀个单独的vue⽂件⾥, 连同相关JS和CSS放到⼀起
-
哪⾥要⽤这个组件,哪⾥导⼊, 当做标签使⽤即可
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 总结
- 为什么需要组件?
答:当遇到⼀段标签(UI)需要复⽤的时候
- 如何让这⼀段标签(UI)复⽤?
答:抽离 -> 封装( JS+HTML+CSS ) -> 导⼊ -> 使⽤
2. 组件及组件化
2.1 组件
组件是⼀个 独⽴的、可复⽤ 的 Vue 实例,也是⼀段 独⽴的 UI 视图 ,代码上体现在是⼀个独⽴的.vue ⽂件,包含 JS+HTML+CSS 3 部分组成。
类似乐⾼和积⽊⼀样,我们可以通过任意的乐⾼或积分进⾏组合,拼装成我们需要的成品。
2.2 组件化
-
定义:⼀种代码的开发思想,体现在⼀个⻚⾯可以拆分成⼀个个组件,每个组件有着⾃⼰独⽴的结构、样式、⾏为; 通过 组件的组合与拼装 形成⼀个完整的⻚⾯,本质是代码的⼀种拆分思想,化⼤为⼩、化繁为简、分⽽治之
-
好处:各⾃独⽴、便于复⽤
2.3 总结
- 什么是组件
答:⼀个 可复⽤的、独⽴的 Vue 实例(UI) ,包含 3 部分代码
- 组件化的好处是什么?
答: 化⼤为⼩、化繁为简 , 利于代码复⽤和维护
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 总结
- 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 总结
- A组件内部导⼊组件能在B组件使⽤吗?
答: no, 不能
- 使⽤组件⾃定义标签 时应该按照什么命名法?
答: 1、⼤驼峰法
2、烤串法
5. 组件的全局注册
5.1 特点
全局注册的组件,在项⽬的任何组件中都能使⽤
5.2 步骤
-
创建.vue组件(三个组成部分)
-
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 总结
- 全局注册组件在任何⼀个组件中可不可以⽤?
答:Yes,⼀旦注册,任意 .vue 中都可⽤
⼆、组件⽣命周期
1. ⽣命周期介绍
1.1 思考
- 什么时候可以发送初始化渲染请求?(越早越好)
- 什么时候可以开始操作DOM?(⾄少DOM得渲染出来)
1.2 概念
就是⼀个Vue实例(组件)从 创建 到 卸载 的整个过程
1.3 四个阶段
⽣命周期四个阶段:① 创建 ② 挂载 ③ 更新 ④ 卸载
-
创建阶段:创建响应式数据
-
挂载阶段:渲染模板
-
更新阶段:修改数据,更新视图
-
卸载阶段:卸载组件
1.4 总结
- 什么是vue的⽣命周期?
答:组件从 创建到卸载 的过程
- 组件的⽣命周期共经历哪⼏个阶段?
答: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 总结
- ⽣命周期钩⼦函数的作⽤?
答: 钩⼦函数在特定时机会 ⾃动执⾏ , 给了开发者在不同时机有 添加⾃⼰代码 的机会
- 选项式API下, 组件⾸次渲染,会执⾏哪⼏个钩⼦?
答:5个, setup/beforeCreate/created/beforeMount/mounted
- 选项式API下, 如果⼀进⼊组件就向后端发请求,在哪个钩⼦进⾏?
答: created
- 选项式API下, 最早可以操作原⽣DOM, 在哪个钩⼦进⾏?
答:mounted
- 选项式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 总结
- 组合式API下, 如果⼀进⼊组件就向后端发请求,在哪个钩⼦进⾏?
答: setup
- 组合式API下, 最早可以操作原⽣DOM, 在哪个钩⼦进⾏?
答: onMounted
- 组合式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>