百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

【Vue3 基础】05.组件化_vue中组件

myzbx 2025-09-18 04:59 18 浏览

这是 Vue3 + Vite + Pinia +TS + Element-Plus 实战系列文档。最近比较忙没什么时间写文章,争取早日把这个系列完结吧~

生命周期和模板引用

在本章之前,我们通过响应式 api 和声明式渲染,处理了 DOM 的更新,但光是这些,对于一些复杂的需要手动操作 DOM 的情况,之前介绍的就无法满足了。

生命周期

每个 Vue 组件在创建时经历的一系列初始化步骤的阶段,我们需要在这些阶段做额外操作的话,需要调用对应阶段的钩子。

这些阶段包括:设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM 等。Vue 官方给出了图示,可以帮助我们更好的理解生命周期:

  1. setup:红色的所有生命周期 API 都在组件的 setup() 阶段被同步调用。
  2. 红色方框:不同阶段代表的生命周期,后续我们写的 生命周期钩子 会在此阶段执行。
  3. 主轴上的:代表组件从初始化到卸载的主要事件。

这里我们先简单介绍,在介绍完生命周期钩子之后,相信你会更理解这张图。

生命周期钩子

了解了上述的生命周期,我们想在对应的周期做一些事情的话,在 Vue3 中我们使用 onXxx 的生命周期钩子,例如:

<script setup>

import { onMounted, onBeforeUnmount } from 'vue'

onMounted(() => {

})

onBeforeUnmount(()=>{

})

</script>

如果你使用过 Vue2 的话,你会发现差别:

<script>

export default {

created() {

},

mounted() {

},

beforeDestroy() {

}

}

</script>

使用方法就如前面写的,在 setup 中,在生命周期 API 中注入回调就可以了。这里我们就不去做 Vue2 和 Vue3 的对比了,全当新学的,按照生命周期的顺序:

  1. setup:beforeCreate 和 created 被 setup 方法代替。
  2. onBeforeMount():在组件被挂载之前执行回调。组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。
  3. onMounted():在组件挂载完成后执行回调。通常用于执行需要访问组件所渲染的 DOM 树相关的副作用。
  4. onBeforeUpdate():在组件即将因为响应式状态变更而更新其 DOM 树之前执行回调。通常用来在 Vue 更新 DOM 之前访问 DOM 状态。
  5. onUpdated():在组件因为响应式状态变更而更新其 DOM 树之后执行回调。会在组件的任意 DOM 更新后被调用,一般用来访问更新后的 DOM,不能在此做更新 DOM 的操作,可能导致循环。
  6. onBeforeUnmount():在组件实例被卸载之前执行回调。
  7. onUnmounted():在组件实例被卸载后执行回调。通常用于手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。
  8. onActivated():当作为 keep-alive 组件被激活时执行回调。
  9. onDeactivated():当作为 keep-alive 组件被取消激活时执行回调。
  10. onErrorCaptured():在捕获了后代组件传递的错误时执行回调。通常用来更改组件状态来为用户显示一个错误状态。
  11. onRenderTracked() 仅开发模式使用 :当组件渲染过程中追踪到响应式依赖时执行回调。通常用于追踪依赖的调试。
  12. onRenderTriggered() 仅开发模式使用 :当响应式依赖的变更触发了组件渲染时执行回调。通常用于触发更新的调试。

以上就是所有的生命周期以及其可被调用的生命周期钩子,我们在上述钩子中传递回调,Vue 会在其所在的生命周期触发。

模板引用 ref

ref 用于注册元素或者子组件的引用。

模板引用将存储在与名字匹配的 ref 中,例如想在数据加载完之后,更改文字信息或描述信息:

<script setup>

import { ref, onMounted } from "vue";

const h2 = ref(null);

const img = ref(null);

onMounted(() => {

setTimeout(() => {

h2.value.textContent = "数据加载完成";

img.value.src = "/src/assets/logo.svg";

}, 3000);

});

</script>

<template>

<h2 ref="h2">数据加载中...</h2>

<img ref="img" src="./assets/load.svg" alt="" />

</template>

当然我们也完全可以让上述的代码中 h2 变成响应式的,并在h2中使用 {{}} 模板语法实现。

组件引用ref

ref 也可以使用在子组件上,相对来说也是较为常见的用法。

首先解释什么是组件:

在此之前我们都是使用的一个单文件App.vue,如果一个项目将代码全写在这一个文件,那将非常难维护,于是我们把可复用等的页面组件化,页面和逻辑抽离,通过导入组件被其它页面引用。

<script setup>

import Child from './Child.vue'

</script>

<template>

<Child ref="child" />

</template>

使用 ref,父组件能获取子组件示例:

<script setup>

import { ref, onMounted } from 'vue'

import Child from './Child.vue'

const child = ref(null)

onMounted(() => {

})

</script>

<template>

<Child ref="child" />

</template>

需要注意的是,子组件没有使用 <script setup> ,被引用的组件实例和该子组件的 this 完全一致,父组件拥有对子组件的每个属性和方法的访问权。

如果使用 <script setup> 那么子组件默认私有,除非使用 defineExpose 显式暴露。

当然在大部分情况下,使用 props 和 emit 就能实现父子组件的交互,而无需使用 ref。

组件传值 props

组件之间传值的方式主要可以概括为这三类:父子组件传值、兄弟组件传值和远亲组件传值。

Vue 提供给我们组件传值的api有两种:props、emit。其中我们可以通过 props 进行父=>子组件传值。

在开发过程中,我们需要通过 defineProps() 明确子组件的 props。父组件可以像声明 HTML 参数一样传值,也可以使用 : (v-bind 简写) 动态传值:

<!-- 父组件 -->

<script setup>

import { ref } from "vue";

import Children from "./components/Children.vue";

const hello = ref("Hello");

</script>

<template>

<input v-model="hello" />

<Children msg="hhh" :activeMsg="hello" />

</template>

<!-- 子组件 -->

<script lang="ts" setup>

import { onMounted } from "vue";

const props = defineProps({

msg: String,

activeMsg: String

});

onMounted(() => {

console.log("props", props);

});

</script>

<template>

<span>msg: {{ msg }}</span>

<span>activeMsg: {{ activeMsg }}</span>

</template>

defineProps() 声明之后,其中的数据就可以在子组件模板中使用。在 JavaScript 中访问则需要通过 defineProps() 返回的对象访问。

注意:

  1. props 是只读的,遵循单项数据流,当尝试修改 props 会警告 prop 只读。
  2. js 中定义数据与 props 中重名时,使用的是 js 中定义的。

const activeMsg = "hl";

<span>activeMsg: {{ activeMsg }}</span>

  1. props 提供校验选项,保证项目没有使用 TypeScript 进行类型检测,也可以确定一定的数据类型,避免不不满足类型要求的数据传入。(现在 Vue3 支持 TypeScript 可以说这个用处不大了)

interface DataProps {

msg: String;

activeMsg: String;

}

const props = defineProps<DataProps>();

组件监听事件 emit

子组件使用 emit() 向父组件传递数据。第一个参数是事件名称,其它额外参数都会被直接传向父组件的监听器函数。

父组件使用 @ (v-on)监听子组件时间,并且可以接收子组件传递的参数。

<!-- 父组件 -->

<template>

<Children @response="(msg) => (hello = msg)" />

{{ hello }}// 点击子组件按钮后变为 hello from child

</template>

<script setup>

import { ref } from "vue";

import Children from "./components/Children.vue";

const hello = ref("Hello");

</script>

<!-- 子组件 -->

<template>

<button @click="emit('response', 'hello from child')">emit</button>

</template>

<script lang="ts" setup>

const emit = defineEmits(["response"]);

</script>

注意:

  1. 在模板中可以使用 $emit 的语法,js中只能使用 defineEmits 返回对象 emit 触发事件。
  2. 可以使用类型标注触发事件,对触发的事件有更精准的控制。

const emit = defineEmits<{

(e: 'response', msg: string): void

}>()

传递模板-插槽 slot

除了传递数据外,父组件还可以通过插槽 slot 的方式将模板传递给子组件。

<!-- 父组件 -->

<template>

<Children>slot button content</Children>

</template>

<script setup>

import Children from "./components/Children.vue";

</script>

<!-- 子组件 -->

<template>

<button><slot /></button>

</template>

<script lang="ts" setup></script>

默认内容

如果想设置默认内容的话,比如在父组件不向子组件传递模板字符,而使用子组件按钮内容有默认 content:

<!-- 父组件 -->

<template>

<Children></Children>

</template>

<script setup>

import Children from "./components/Children.vue";

</script>

<!-- 子组件 -->

<template>

<button>

<slot>

content

</slot>

</button>

</template>

<script lang="ts" setup></script>

具名插槽

如果组件包含多个插槽出口,则需要使用具名插槽,用来给插槽一个唯一 ID,以确定不同出口要渲染的内容。

<!-- 父组件 -->

<template>

<Children>

<template v-slot:header> Header </template>

<template v-slot:button> slot button content </template>

</Children>

</template>

<script setup>

import Children from "./components/Children.vue";

</script>

<template>

<div><slot name="header" /></div>

<button><slot name="button" /></button>

</template>

<script lang="ts" setup></script>

v-slot 可以简写为 # ,v-slot:header => #header。并且v-slot 也可以接受动态参数(动态插槽名): #[dynamicSlotName] 。

作用域插槽

上述的几种插槽,无法访问到子组件的状态,在某些场景中我们想要子组件传递数据给插槽,作用域插槽就可以满足这个需求。

<!-- 父组件 -->

<template>

<Children>

<template v-slot:header="slotProps"> {{ slotProps.msg }}</template>

</Children>

</template>

<script setup>

import Children from "./components/Children.vue";

</script>

<!-- 子组件 -->

<template>

<input type="text" v-model="msg" />

<div><slot name="header" :msg="msg" /></div>

</template>

<script lang="ts" setup>

import { ref } from "vue";

const msg = ref("");

</script>

准确来说,上述例子是具名作用域插槽。如果是普通的作用域插槽,即改变 template 的具名插槽为普通插槽就可以了。

实战

前几节讲到了组件的各生命周期钩子、组件传值的 props、触发事件的 emit 以及模板插槽 slot,用的例子比较简单,我们通过实战体验一下在实战开发中的运用,帮助我们更好的理解和运用。

博客的列表展示功能,提供分类功能。

  1. 分类的数据来源于父组件,其选择的类型通过回调告知父组件。
  2. 列表部分父组件做元素内容和样式的控制,子组件做列表的基础循环等的通用操作。

示例尽可能的包含到我们这章所学知识,如下述代码部分看不懂,代表你那部分知识还没理解清楚。

本节中的例子里包含部分ts的类型确认

父组件 App.vue:

<script lang="ts" setup>

import { reactive, ref, onMounted } from "vue";

import Children from "./components/Children.vue";

import ClassifyHeader from "./components/ClassifyHeader.vue";

const tags = reactive({

list: ["vue", "react"],

checked: [],

});

function checkedTags(checked) {

tags.checked = checked;

getData();

}

onMounted(() => {

getData();

});

const listRef = ref();

function getData() {

const params = { tags: tags.checked, page: 1 };

console.log("请求参数:", params);

setTimeout(() => {

const data = [

{ title: "JavaScript 入门到精通", username: "Chocolate 1999", date: "2023-02-11" },

{ title: "Vue3 实战", username: "HearLing", date: "2023-03-09" },

];

listRef.value.loadData(data);

}, 1000);

}

</script>

<template>

<ClassifyHeader :tags="tags.list" @select="checkedTags" />

<Children ref="listRef">

<template #item="{ title, username, date }">

<div class="item">

<p>{{ title }}</p>

<p class="meta">作者:{{ username }} | 时间:{{ date }}</p>

</div>

</template>

</Children>

</template>

<style scoped></style>

父组件涉及知识点:

  1. 与 ClassifyHeader 分类组件的传值。props 和 emit。
  2. 生命周期 onMounted。在组件挂载后请求数据。
  3. 组件引用 ref 。使用子组件的方法。
  4. 具名作用域插槽。

父组件和分类组件

在父组件中,初始的 tags.list 值通过 props 传递给子组件,同时监听了 select 事件。select 触发会执行 checkedTags 函数,父组件得到 checked 值,并做出重新请求列表数据的操作。


components/ClassifyHeader.vue 分类组件:

<!-- 分类 -->

<template>

<div v-for="item in props.tags">

<input type="checkbox" :value="item" @click="select(item)" />

{{ item }}

</div>

</template>

<script lang="ts" setup>

const props = defineProps(["tags"]);

const emit = defineEmits<{

(e: "select", checked: string[]): void;

}>();

const checked: string[] = [];

function select(item) {

const index = checked.indexOf(item);

if (index !== -1) {

checked.splice(index, 1);

} else {

checked.push(item);

}

emit("select", checked);

}

</script>

<style lang="scss" scoped></style>

ClassifyHeader 分类组件主要就是记录所选类别,并通过 emit 传递数据给父组件。

父组件与列表组件

父组件通过 ref 获取到子组件示例,并且使用子组件暴露的 loadData 方法加载数据。子组件通过作用域插槽,传递数据给父组件,父组件控制内容布局。

components/Children.vue 列表组件:

<!-- 子组件 -->

<script lang="ts" setup>

import { ref } from "vue";

interface Item {

title: string;

username: string;

date: string;

}

const items = ref<Item[]>([]);

const loadData = (data) => {

items.value = data;

};

defineExpose({

loadData,

});

</script>

<template>

<ul>

<li v-if="!items.length">Loading...</li>

<li v-for="item in items">

<slot name="item" v-bind="item" />

</li>

</ul>

</template>

<style scoped></style>

子组件,提供具名插槽 slot 以及用 defineExpose 抛出 loadData 方法供父组件通过组件示例拿到。

上述例子,我们就把大部分的知识都重新实战复习了一遍,建议可以把这几个代码自己写一遍,加深印象。

其它传值方式

除了上述的 props、emit 父子组件传值之外。还可以使用依赖注入 API :provide、inject。

  1. provide():提供一个值,可以被后代组件注入。使用方式:provide(/* 注入名 / 'message', / 值 */ 'hello!') 。
  2. inject():注入上层组件提供的数据。使用方式:const message = inject('message') 。

兄弟组件,可以可以通过父组件控制数据传值。

对于跨组件通信,我们可以使用状态管理工具,比如我们后面要学的 Pinia,就是一个状态管理框架。

总结

本章中,我们首先结合 Vue 的生命周期的流程图,例举了各个生命周期钩子的触发时机,以及部分钩子的使用场景。然后讲到了 ref 的作用,最后讲完并实战了组件通信相关的 api。

至此 Vue3 的基础知识到这里已经结束了,还剩下一小部分,留在我们实战课程中探索。

如果未学习过TypeScript的话,我也准备了 TypeScript 相关的入门基础知识,课程不会很长,带大家了解 TypeScript 常用的一些知识,为实战做准备。

相关推荐

如何设计一个优秀的电子商务产品详情页

加入人人都是产品经理【起点学院】产品经理实战训练营,BAT产品总监手把手带你学产品电子商务网站的产品详情页面无疑是设计师和开发人员关注的最重要的网页之一。产品详情页面是客户作出“加入购物车”决定的页面...

怎么在JS中使用Ajax进行异步请求?

大家好,今天我来分享一项JavaScript的实战技巧,即如何在JS中使用Ajax进行异步请求,让你的网页速度瞬间提升。Ajax是一种在不刷新整个网页的情况下与服务器进行数据交互的技术,可以实现异步加...

中小企业如何组建,管理团队_中小企业应当如何开展组织结构设计变革

前言写了太多关于产品的东西觉得应该换换口味.从码农到架构师,从前端到平面再到UI、UE,最后走向了产品这条不归路,其实以前一直再给你们讲.产品经理跟项目经理区别没有特别大,两个岗位之间有很...

前端监控 SDK 开发分享_前端监控系统 开源

一、前言随着前端的发展和被重视,慢慢的行业内对于前端监控系统的重视程度也在增加。这里不对为什么需要监控再做解释。那我们先直接说说需求。对于中小型公司来说,可以直接使用三方的监控,比如自己搭建一套免费的...

Ajax 会被 fetch 取代吗?Axios 怎么办?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!今天给大家带来的主题是ajax、fetch...

前端面试题《AJAX》_前端面试ajax考点汇总

1.什么是ajax?ajax作用是什么?AJAX=异步JavaScript和XML。AJAX是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,AJAX可以使网页实...

Ajax 详细介绍_ajax

1、ajax是什么?asynchronousjavascriptandxml:异步的javascript和xml。ajax是用来改善用户体验的一种技术,其本质是利用浏览器内置的一个特殊的...

6款可替代dreamweaver的工具_替代powerdesigner的工具

dreamweaver对一个web前端工作者来说,再熟悉不过了,像我07年接触web前端开发就是用的dreamweaver,一直用到现在,身边的朋友有跟我推荐过各种更好用的可替代dreamweaver...

我敢保证,全网没有再比这更详细的Java知识点总结了,送你啊

接下来你看到的将是全网最详细的Java知识点总结,全文分为三大部分:Java基础、Java框架、Java+云数据小编将为大家仔细讲解每大部分里面的详细知识点,别眨眼,从小白到大佬、零基础到精通,你绝...

福斯《死侍》发布新剧照 &quot;小贱贱&quot;韦德被改造前造型曝光

时光网讯福斯出品的科幻片《死侍》今天发布新剧照,其中一张是较为罕见的死侍在被改造之前的剧照,其余两张剧照都是死侍在执行任务中的状态。据外媒推测,片方此时发布剧照,预计是为了给不久之后影片发布首款正式预...

2021年超详细的java学习路线总结—纯干货分享

本文整理了java开发的学习路线和相关的学习资源,非常适合零基础入门java的同学,希望大家在学习的时候,能够节省时间。纯干货,良心推荐!第一阶段:Java基础重点知识点:数据类型、核心语法、面向对象...

不用海淘,真黑五来到你身边:亚马逊15件热卖爆款推荐!

Fujifilm富士instaxMini8小黄人拍立得相机(黄色/蓝色)扫二维码进入购物页面黑五是入手一个轻巧可爱的拍立得相机的好时机,此款是mini8的小黄人特别版,除了颜色涂装成小黄人...

2025 年 Python 爬虫四大前沿技术:从异步到 AI

作为互联网大厂的后端Python爬虫开发,你是否也曾遇到过这些痛点:面对海量目标URL,单线程爬虫爬取一周还没完成任务;动态渲染的SPA页面,requests库返回的全是空白代码;好不容易...

最贱超级英雄《死侍》来了!_死侍超燃

死侍Deadpool(2016)导演:蒂姆·米勒编剧:略特·里斯/保罗·沃尼克主演:瑞恩·雷诺兹/莫蕾娜·巴卡林/吉娜·卡拉诺/艾德·斯克林/T·J·米勒类型:动作/...

停止javascript的ajax请求,取消axios请求,取消reactfetch请求

一、Ajax原生里可以通过XMLHttpRequest对象上的abort方法来中断ajax。注意abort方法不能阻止向服务器发送请求,只能停止当前ajax请求。停止javascript的ajax请求...