文章目录
vue 组件化开发
一、什么是Vue组件化开发
- 概念
- Vue组件化开发是一种将用户界面(UI)拆分成独立、可复用的小块(即组件)的开发模式。每个组件都包含了自己的HTML模板(视图)、JavaScript逻辑(行为)和CSS样式(外观),就像是一个独立的小部件。
- 例如,一个电商网站中,商品列表、商品详情页、购物车等模块都可以分别作为一个组件来开发。
- 优势
- 可维护性高:当项目规模变大时,如果所有代码都写在一个文件中,将会变得非常混乱,难以维护。而组件化可以将复杂的界面按照功能模块划分,每个组件职责明确,方便开发者理解和修改。
- 可复用性强:一个设计良好的组件可以在项目的多个地方甚至不同项目中重复使用。比如一个按钮组件,只要样式和功能符合要求,就可以在登录页、注册页等多处使用。
- 团队协作方便:不同的开发人员可以负责不同的组件开发,并行工作,提高开发效率。
二、组件的创建方式
- 全局组件
- 定义:在Vue应用中,全局组件可以在任何地方使用。
- 示例代码
// 创建一个名为 'my-component' 的全局组件
Vue.component('my-component', {
template: '<div>这是一个全局组件</div>'
});
- 在上述代码中,
Vue.component()
方法用于创建全局组件。第一个参数是组件的名称(注意名称最好使用短横线分隔命名),第二个参数是一个对象,对象中的template
属性定义了组件的HTML模板内容。 - 使用方式:在HTML文件中,可以像使用普通HTML标签一样使用这个组件。例如:
<my-component></my-component>
- 局部组件
- 定义:局部组件只能在定义它的组件内部使用。
- 示例代码
// 创建一个Vue实例
var app = new Vue({
el: '#app',
components: {
'local-component': {
template: '<div>这是一个局部组件</div>'
}
}
});
- 在这里,
components
属性是一个对象,对象中的键值对定义了局部组件。键是组件的名称,值是组件的定义对象,包括template
等属性。
- 使用方式:只能在el
指定的#app
这个DOM元素内部使用,如:
<div id="app">
<local-component></local-component>
</div>
三、组件的数据传递
- 父组件向子组件传递数据(props)
- 概念:
props
是组件的自定义属性,用于接收来自父组件的数据。 - 示例代码
- 父组件
- 概念:
// 父组件
Vue.component('parent-component', {
template: `
<div>
<child-component :message="parentMessage"></child-component>
</div>
`,
data() {
return {
parentMessage: "这是父组件传递的数据"
}
}
});
- 子组件
// 子组件
Vue.component('child-component', {
props: ['message'],
template: '<div>{{ message }}</div>'
});
- 在这个例子中,父组件通过
:message="parentMessage"
的方式将parentMessage
数据传递给子组件,子组件通过props
数组接收这个数据,并在模板中展示。
- 子组件向父组件传递数据(自定义事件)
- 概念:子组件通过触发自定义事件,将数据传递给父组件。
- 示例代码
- 子组件
Vue.component('child-component', {
template: `
<div>
<button @click="sendData">向父组件传递数据</button>
</div>
`,
methods: {
sendData() {
this.$emit('child-data', "这是子组件传递的数据");
}
}
});
- 父组件
Vue.component('parent-component', {
template: `
<div>
<child-component @child - data="receiveData"></child-component>
</div>
`,
methods: {
receiveData(data) {
console.log(data);
}
}
});
- 子组件通过
$emit
方法触发child - data
自定义事件,并传递数据。父组件通过@child - data
监听这个事件,并在receiveData
方法中接收数据。
四、组件的生命周期
- 概念:组件的生命周期钩子函数可以让开发者在组件的不同阶段执行特定的代码。例如,在组件创建时进行数据初始化,在组件销毁时清理资源等。
- 主要生命周期钩子函数
beforeCreate
:在实例初始化之后,数据观测(data observer
)和event/watcher
事件配置之前被调用。此时,组件的选项对象(options
)有了,但是data
和methods
等还没初始化好。created
:组件实例创建完成后立即调用。此时,data
和methods
等都已经初始化好了,但是还没有开始渲染模板。可以在这里进行数据的获取、初始化等操作。beforeMount
:在挂载开始之前被调用,此时template
模板已经编译好了,但是还没有将其挂载到真实的DOM上。mounted
:挂载完成后调用。此时,组件已经渲染到真实的DOM中,可以进行DOM操作,如获取组件中的某个元素的高度、宽度等。beforeUpdate
:数据更新时,在虚拟DOM重新渲染和打补丁之前调用。可以在这里进行一些在数据更新时,需要提前处理的事情。updated
:数据更新完成后,虚拟DOM重新渲染和打补丁之后调用。beforeDestroy
:在组件销毁之前调用。可以在这里清理定时器、解绑事件监听器等资源。destroyed
:组件销毁后调用。
五、组件的插槽(Slot)
- 概念:插槽用于在组件中预留一个位置,以便父组件可以向子组件传递内容。
- 示例代码
- 子组件
Vue.component('slot - component', {
template: `
<div>
<slot></slot>
</div>
`
});
- **父组件**
Vue.component('parent - slot - component', {
template: `
<div>
<slot - component>
<p>这是父组件传递到插槽中的内容</p>
</slot - component>
</div>
`
});
- 在这个例子中,子组件
slot - component
通过<slot>
标签预留了一个插槽位置。父组件parent - slot - component
在使用子组件时,将<p>这是父组件传递到插槽中的内容</p>
传递到了子组件的插槽中。
数据传递的方式实例
-
父组件向子组件传递数据(Props)
- 应用场景:当需要在父子组件之间共享数据,且子组件依赖父组件的数据来进行渲染或其他操作时使用。例如,在一个商品列表组件(父组件)和商品详情组件(子组件)之间,父组件将商品数据传递给子组件用于展示详情。
- 实例
- 父组件(
ProductList.vue
)<template> <div> <ProductDetail :product="selectedProduct" /> </div> </template> <script setup> import ProductDetail from './ProductDetail.vue'; import { ref } from 'vue'; const selectedProduct = ref({ id: 1, name: '商品1', price: 19.99 }); </script>
- 子组件(
ProductDetail.vue
)<template> <div> <h2>{{ product.name }}</h2> <p>价格: {{ product.price }}</p> </div> </template> <script setup> import { defineProps } from 'vue'; const props = defineProps({ product: Object }); </script>
- 在这个例子中,父组件
ProductList.vue
通过:product="selectedProduct"
将selectedProduct
数据传递给子组件ProductDetail.vue
。子组件通过defineProps
定义接收product
属性,其类型为Object
,然后在模板中使用product
数据进行展示。
- 父组件(
-
子组件向父组件传递数据(自定义事件)
- 应用场景:当子组件中的操作需要通知父组件并传递相关数据时,例如,在一个表单组件(子组件)中用户提交数据后,需要将数据传递给父组件进行处理。
- 实例
- 子组件(
FormComponent.vue
)<template> <form @submit.prevent="submitForm"> <input v - model="formData.name" placeholder="姓名" /> <input v - model="formData.age" placeholder="年龄" /> <button type="submit">提交</button> </form> </template> <script setup> import { defineEmits, ref } from 'vue'; const emits = defineEmits(['formSubmitted']); const formData = ref({ name: '', age: '' }); const submitForm = () => { emits('formSubmitted', formData.value); }; </script>
- 父组件(
App.vue
)<template> <div> <FormComponent @formSubmitted="handleFormSubmit" /> </div> </template> <script setup> import FormComponent from './FormComponent.vue'; const handleFormSubmit = (data) => { console.log('收到表单数据:', data); }; </script>
- 在子组件
FormComponent.vue
中,通过defineEmits
定义了formSubmitted
事件。在submitForm
方法中,当表单提交时,通过emits('formSubmitted', formData.value)
触发事件并传递formData.value
(表单数据)。父组件App.vue
通过@formSubmitted="handleFormSubmit"
监听这个事件,并在handleFormSubmit
方法中接收数据进行处理。
- 子组件(
-
兄弟组件之间的数据传递(通过事件总线或共享状态)
-
应用场景:当两个兄弟组件需要共享数据或者一个组件的操作需要影响另一个兄弟组件时使用。例如,在一个购物车应用中,商品列表组件和购物车组件是兄弟组件,添加商品到购物车的操作需要在两个组件之间共享数据。
-
使用事件总线(mitt库)实例
- 安装mitt库:首先需要安装
mitt
库,在项目目录下运行npm install mitt
。 - 创建事件总线(
event - bus.js
)import mitt from'mitt'; const emitter = mitt(); export default emitter;
- 组件A(
ProductList.vue
)<template> <div> <button @click="addProductToCart(product)">添加到购物车</button> </div> </template> <script setup> import { ref } from 'vue'; import emitter from './event - bus.js'; const product = ref({ id: 1, name: '商品1', price: 19.99 }); const addProductToCart = (product) => { emitter.emit('productAdded', product); }; </script>
- 组件B(
Cart.vue
)<template> <div> <ul> <li v - for="product in cartProducts" :key="product.id"> {{ product.name }} </li> </ul> </div> </template> <script setup> import { ref } from 'vue'; import emitter from './event - bus.js'; const cartProducts = ref([]); emitter.on('productAdded', (product) => { cartProducts.value.push(product); }); </script>
- 在这个例子中,通过
mitt
库创建了一个事件总线emitter
。组件A(ProductList.vue
)在点击按钮时,通过emitter.emit('productAdded', product)
发送一个productAdded
事件,并传递商品数据。组件B(Cart.vue
)通过emitter.on('productAdded', (product) => {...})
监听这个事件,当接收到事件时,将商品数据添加到cartProducts
数组中,用于展示购物车中的商品。
- 安装mitt库:首先需要安装
-
使用共享状态(Pinia)实例
- 安装Pinia:运行
npm install pinia
。 - 创建store(
store.js
)import { defineStore } from 'pinia'; export const useCartStore = defineStore('cart', { state: () => ({ cartProducts: [] }), actions: { addProduct(product) { this.cartProducts.push(product); } } });
- 组件A(
ProductList.vue
)<template> <div> <button @click="addProductToCart(product)">添加到购物车</button> </div> </template> <script setup> import { ref } from 'vue'; import { useCartStore } from './store.js'; const product = ref({ id: 1, name: '商品1', price: 19.99 }); const addProductToCart = () => { const cartStore = useCartStore(); cartStore.addProduct(product.value); }; </script>
- 组件B(
Cart.vue
)<template> <div> <ul> <li v - for="product in cartProducts" :key="product.id"> {{ product.name }} </li> </ul> </div> </template> <script setup> import { computed } from 'vue'; import { useCartStore } from './store.js'; const cartStore = useCartStore(); const cartProducts = computed(() => cartStore.cartProducts); </script>
- 在这里,通过
Pinia
创建了一个store
,其中包含了cartProducts
状态和addProduct
方法。组件A(ProductList.vue
)通过useCartStore
获取store
实例,调用addProduct
方法将商品添加到购物车。组件B(Cart.vue
)通过computed
获取cartProducts
状态用于展示购物车中的商品。这种方式利用了状态管理库来实现兄弟组件之间的数据共享和传递。
- 安装Pinia:运行
-
Provide/Inject 深层组件传递
1. 基础概念provide
函数:在组件中使用provide
来提供数据。这个函数可以在组件的<script setup>
或者普通的setup
函数中调用。它接受两个参数,第一个参数是数据的键(可以是一个字符串或者一个Symbol),第二个参数是要提供的数据本身。inject
函数:在深层的组件中使用inject
来接收由上层组件提供的数据。它接受一个参数,即provide
函数中定义的数据键,用于获取对应的被提供的数据。如果没有找到对应的提供数据,inject
还可以接受一个默认值作为第二个参数。
-
响应式数据传递示例
- 应用场景:当需要在深层嵌套的组件之间传递响应式数据,例如在多层级的菜单组件中传递当前选中的菜单项状态,或者在复杂的表单组件嵌套结构中传递表单数据。
- 实例
- 顶层组件(
App.vue
)<template> <div> <Header /> <MainContent /> </div> </template> <script setup> import Header from './Header.vue'; import MainContent from './MainContent.vue'; import { ref } from 'vue'; const selectedItem = ref('首页'); provide('selectedItem', selectedItem); </script>
- 中间层组件(
MainContent.vue
)<template> <div> <Sidebar /> <Article /> </div> </template> <script setup> import Sidebar from './Sidebar.vue'; import Article from './Article.vue'; // 不需要接收和重新传递selectedItem,因为下层组件可以直接inject获取 </script>
- 深层组件(
Article.vue
)<template> <div> <p>当前选中的菜单项是: {{ selectedItem }}</p> </div> </template> <script setup> import { inject } from 'vue'; const selectedItem = inject('selectedItem'); </script>
- 在这个例子中,
App.vue
是顶层组件,通过ref
创建了一个响应式数据selectedItem
,并使用provide('selectedItem', selectedItem)
将其提供给下层组件。MainContent.vue
作为中间层组件,不需要对这个数据进行特殊处理。Article.vue
是深层组件,通过inject('selectedItem')
获取了顶层组件提供的响应式数据,并在模板中展示。当App.vue
中selectedItem
的值发生变化时,Article.vue
中的展示内容也会相应更新。
- 顶层组件(
-
传递函数示例
- 应用场景:在多层组件之间共享方法,例如在一个具有复杂布局的应用中,需要从深层组件触发顶层组件中的某个操作,如提交表单、切换主题等。
- 实例
- 顶层组件(
App.vue
)<template> <div> <Header /> <MainContent /> </div> </template> <script setup> import Header from './Header.vue'; import MainContent from './MainContent.vue'; const changeTheme = () => { console.log('主题已切换'); }; provide('changeTheme', changeTheme); </script>
- 中间层组件(
MainContent.vue
)<template> <div> <Sidebar /> <Article /> </div> </template> <script setup> import Sidebar from './Sidebar.vue'; import Article from './Article.vue'; // 不需要接收和重新传递changeTheme,因为下层组件可以直接inject获取 </script>
- 深层组件(
Article.vue
)<template> <div> <button @click="triggerThemeChange">切换主题</button> </div> </template> <script setup> import { inject } from 'vue'; const changeTheme = inject('changeTheme'); const triggerThemeChange = () => { changeTheme(); }; </script>
- 在这个例子中,
App.vue
提供了一个changeTheme
函数,用于切换主题(这里只是简单地打印日志)。Article.vue
通过inject
获取这个函数,并在按钮的点击事件triggerThemeChange
中调用它。这样,深层组件就可以触发顶层组件提供的操作。
- 顶层组件(
-
Symbol作为键的示例(避免键名冲突)
- 应用场景:在大型应用中,当有多个组件可能提供和注入数据,为了避免键名冲突,可以使用Symbol作为
provide
和inject
的键。例如,在一个包含多个第三方组件库的项目中,或者在多个团队协作开发的大型模块中。 - 实例
- 定义Symbol键(
keys.js
)export const MY_DATA_KEY = Symbol('my - data - key');
- 顶层组件(
App.vue
)<template> <div> <Header /> <MainContent /> </div> </template> <script setup> import Header from './Header.vue'; import MainContent from './MainContent.vue'; import { ref } from 'vue'; import { MY_DATA_KEY } from './keys.js'; const myData = ref('这是我的数据'); provide(MY_DATA_KEY, myData); </script>
- 深层组件(
Article.vue
)<template> <div> <p>接收到的数据: {{ myData }}</p> </div> </template> <script setup> import { inject } from 'vue'; import { MY_DATA_KEY } from './keys.js'; const myData = inject(MY_DATA_KEY); </script>
- 在这个例子中,首先在
keys.js
文件中定义了一个SymbolMY_DATA_KEY
。App.vue
使用这个Symbol作为键来提供myData
数据。Article.vue
通过同样的Symbol键来注入数据,这样可以有效避免键名冲突,因为Symbol是唯一的。
- 定义Symbol键(
- 应用场景:在大型应用中,当有多个组件可能提供和注入数据,为了避免键名冲突,可以使用Symbol作为
-
组件生命周期应用场景
onMounted
生命周期钩子- 应用场景
- DOM操作初始化:当组件挂载到DOM后,可能需要获取组件中的DOM元素来进行一些初始化操作,比如设置焦点、初始化第三方插件等。例如,在一个表单组件挂载后,自动将焦点设置到第一个输入框。
- 数据获取和初始化:在组件挂载完成后,从服务器获取数据来填充组件内容是很常见的场景。例如,加载一个用户信息组件时,在挂载后发送请求获取用户详细信息并展示。
- 实例
- 自动聚焦输入框
<template> <input ref="inputRef" /> </template> <script setup> import { ref, onMounted } from 'vue'; const inputRef = ref(null); onMounted(() => { if (inputRef.value) { inputRef.value.focus(); } }); </script>
- 在这个例子中,我们在组件模板中定义了一个
input
元素,并通过ref
获取其引用存储在inputRef
中。在onMounted
钩子函数中,检查inputRef.value
是否存在(即组件已经挂载,input
元素已经在DOM中),如果存在则调用focus
方法将焦点设置到该输入框。
- 在这个例子中,我们在组件模板中定义了一个
- 获取用户信息
<template> <div> <p>用户姓名: {{ user.name }}</p> <p>用户年龄: {{ user.age }}</p> </div> </template> <script setup> import { ref, onMounted } from 'vue'; const user = ref({}); onMounted(() => { // 模拟从服务器获取用户数据 fetch('https://example.com/api/user') .then((response) => response.json()) .then((data) => { user.value = data; }); }); </script>
- 这里定义了一个
user
的响应式数据。在onMounted
钩子函数中,使用fetch
函数从一个模拟的API端点获取用户数据,然后将获取到的数据赋值给user.value
,这样组件就可以展示用户的信息。
- 这里定义了一个
- 自动聚焦输入框
- 应用场景
onUpdated
生命周期钩子- 应用场景
- 响应式数据更新后的DOM操作:当组件中的响应式数据更新并导致DOM重新渲染后,可能需要对更新后的DOM进行操作。例如,根据更新后的内容重新计算元素的位置或尺寸。
- 数据更新后的副作用处理:某些情况下,数据更新可能会引发一些副作用,如更新图表数据后需要重新绘制图表,在
onUpdated
阶段可以执行这些操作。
- 实例
- 更新后重新计算元素高度
<template> <div ref="contentDiv"> <p>{{ content }}</p> </div> </template> <script setup> import { ref, onUpdated } from 'vue'; const content = ref('初始内容'); const contentDiv = ref(null); onUpdated(() => { if (contentDiv.value) { console.log('内容更新后,组件高度:', contentDiv.value.offsetHeight); } }); const updateContent = () => { content.value = '更新后的内容'; }; </script>
- 定义了一个
content
的响应式数据和一个contentDiv
用于引用div
元素。在onUpdated
钩子函数中,当content
更新导致DOM重新渲染后,检查contentDiv.value
是否存在,如果存在则打印其高度。通过updateContent
方法可以更新content
数据,触发onUpdated
钩子。
- 定义了一个
- 重新绘制图表(假设使用了一个简单的图表库)
<template> <div ref="chartDiv"></div> </template> <script setup> import { ref, onUpdated } from 'vue'; import { drawChart } from './chart - utils'; const chartData = ref([1, 2, 3]); const chartDiv = ref(null); onUpdated(() => { if (chartDiv.value) { drawChart(chartDiv.value, chartData.value); } }); const updateChartData = () => { chartData.value = [4, 5, 6]; }; </script>
- 定义了
chartData
作为图表数据的响应式数据和chartDiv
用于引用图表所在的div
元素。在onUpdated
钩子函数中,当chartData
更新后,检查chartDiv.value
是否存在,如果存在则调用drawChart
函数(假设这是一个自定义的绘制图表函数)重新绘制图表。updateChartData
方法用于更新图表数据,触发onUpdated
钩子。
- 定义了
- 更新后重新计算元素高度
- 应用场景
onBeforeUnmount
生命周期钩子- 应用场景
- 清理资源:在组件销毁之前,需要清理一些资源,如定时器、事件监听器、网络请求等。例如,如果组件中有一个定时器用于定时更新数据,在组件销毁前需要清除这个定时器,以防止内存泄漏。
- 保存状态(可选):有时候可能需要在组件销毁前保存一些状态,以便下次创建类似组件时可以恢复。
- 实例
- 清除定时器
<template> <div> <p>倒计时: {{ countDown }}</p> </div> </template> <script setup> import { ref, onBeforeUnmount } from 'vue'; const countDown = ref(10); let timer; const startCountDown = () => { timer = setInterval(() => { countDown.value--; if (countDown.value === 0) { clearInterval(timer); } }, 1000); }; onBeforeUnmount(() => { if (timer) { clearInterval(timer); } }); </script>
- 定义了一个
countDown
的响应式数据用于倒计时。通过startCountDown
方法启动一个定时器,每秒减少countDown.value
的值,当倒计时结束时清除定时器。在onBeforeUnmount
钩子函数中,检查定时器是否存在,如果存在则清除它,以确保在组件销毁前清理资源。
- 定义了一个
- 保存组件状态(简单示例)
<template> <div> <p>输入内容: {{ inputValue }}</p> </div> </template> <script setup> import { ref, onBeforeUnmount, localStorage } from 'vue'; const inputValue = ref(''); onBeforeUnmount(() => { localStorage.setItem('lastInputValue', inputValue.value); }); </script>
- 定义了一个
inputValue
的响应式数据。在onBeforeUnmount
钩子函数中,将inputValue.value
保存到本地存储localStorage
中,这样下次打开类似组件时可以获取并恢复这个状态。不过在实际应用中,可能需要更复杂的状态管理机制。
- 定义了一个
- 清除定时器
- 应用场景
插槽应用
-
插槽概念理解
- 插槽(Slot):插槽是Vue组件中一种强大的机制,用于在组件内部定义一个可以被外部内容填充的占位符。它允许父组件向子组件传递自定义内容,从而增强了组件的复用性和灵活性。
-
匿名插槽(默认插槽)应用实例
- 应用场景:当子组件中有一个通用的区域,希望父组件能够自由地填充内容时,可以使用匿名插槽。例如,一个通用的卡片组件,中间的主要内容部分可以由父组件决定。
- 实例
- 子组件(
CardComponent.vue
)
<template> <div class="card"> <header> <slot name="header">默认头部标题</slot> </header> <main> <slot>默认主要内容</slot> </main> <footer> <slot name="footer">默认底部文本</slot> </footer> </div> </template> <style scoped> .card { border: 1px solid #ccc; border - radius: 5px; padding: 10px; } </style>
- 父组件(
App.vue
)<template> <CardComponent> <p>这是自定义的主要内容,填充到匿名插槽中。</p> </CardComponent> </template> <script setup> import CardComponent from './CardComponent.vue'; </script>
- 在这个例子中,子组件
CardComponent.vue
定义了一个卡片布局,其中<main>
部分的插槽没有指定名称,这就是匿名插槽。父组件App.vue
在使用CardComponent
时,内部的<p>这是自定义的主要内容,填充到匿名插槽中。</p>
会被填充到子组件的匿名插槽中,替换掉默认主要内容。
- 子组件(
-
具名插槽应用实例
- 应用场景:当子组件中有多个不同用途的占位符,需要父组件分别向这些不同的位置传递内容时,就需要使用具名插槽。比如一个布局组件,有头部、主体和底部三个区域,每个区域的内容和样式可能都不同,通过具名插槽可以清晰地传递内容。
- 实例
- 子组件(
LayoutComponent.vue
)
<template> <div class="layout"> <slot name="header"></slot> <slot name="main"></slot> <slot name="footer"></slot> </div> </template> <style scoped> .layout { display: flex; flex - direction: column; height: 100vh; } </style>
- 父组件(
App.vue
)<template> <LayoutComponent> <template v - slot:header> <h1>这是头部标题,填充到具名插槽header中。</h1> </template> <template #main> <p>这是主体内容,填充到具名插槽main中。</p> </template> <template v - slot:footer> <p>这是底部文本,填充到具名插槽footer中。</p> </template> </LayoutComponent> </template> <script setup> import LayoutComponent from './LayoutComponent.vue'; </script>
- 在子组件
LayoutComponent.vue
中,定义了三个具名插槽header
、main
和footer
。父组件App.vue
在使用LayoutComponent
时,通过<template v - slot:header>
、<template #main>
和<template v - slot:footer>
分别向子组件的三个具名插槽传递不同的内容,以构建完整的页面布局。这里v - slot
是一种较旧的写法,#
是Vue 3.0+引入的更简洁的写法,它们的作用是相同的。
- 子组件(
-
作用域插槽应用实例
- 应用场景:当子组件需要将自己内部的数据传递给父组件,让父组件根据这些数据来渲染插槽内容时,就需要使用作用域插槽。例如,一个列表组件,子组件有列表数据,但是希望父组件来决定如何渲染每个列表项的样式。
- 实例
- 子组件(
ListComponent.vue
)<template> <ul> <li v - for="item in listData" :key="item.id"> <slot :item="item">{{ item.name }}</slot> </li> </ul> </template> <script setup> import { ref } from 'vue'; const listData = ref([ { id: 1, name: '项目1' }, { id: 2, name: '项目2' } ]); </script>
- 父组件(
App.vue
)<template> <ListComponent> <template v - slot:default="slotProps"> <strong>{{ slotProps.item.name }}</strong> </template> </ListComponent> </template> <script setup> import ListComponent from './ListComponent.vue'; </script>
- 在子组件
ListComponent.vue
中,通过<slot :item="item">{{ item.name }}</slot>
将item
数据传递给插槽。在父组件App.vue
中,使用<template v - slot:default="slotProps">
接收这个数据,其中slotProps
是一个包含了从子组件传递过来的数据的对象,在这里可以通过slotProps.item.name
来获取并渲染每个列表项的内容,并且在这个例子中使用<strong>
标签来加粗显示列表项名称。这里v - slot:default
也可以写成#default
,它们都表示默认插槽,因为在子组件中没有为插槽指定名称,所以默认是default
。
- 子组件(
define 相关应用
-
defineProps
- 应用场景
- 主要用于在Vue 3.5组件中接收来自父组件传递的数据,实现父子组件之间的数据通信。这种方式使得组件的接口更加清晰,父组件可以通过
props
将数据传递给子组件,子组件明确知道自己接收了哪些数据。
- 主要用于在Vue 3.5组件中接收来自父组件传递的数据,实现父子组件之间的数据通信。这种方式使得组件的接口更加清晰,父组件可以通过
- 实例
- 父组件(
ParentComponent.vue
)<template> <div> <ChildComponent :message="parentMessage" /> </div> </template> <script setup> import ChildComponent from './ChildComponent.vue'; const parentMessage = '这是父组件传递的消息'; </script>
- 子组件(
ChildComponent.vue
)<template> <div> <p>{{ message }}</p> </div> </template> <script setup> import { defineProps } from 'vue'; const props = defineProps({ message: String }); </script>
- 在这个例子中,父组件通过
:message="parentMessage"
将parentMessage
数据传递给子组件。子组件使用defineProps
定义了一个名为message
的props
,其数据类型为String
,然后在模板中通过{{ message }}
展示了这个数据。
- 父组件(
- 应用场景
-
defineEmits
- 应用场景
- 用于子组件向父组件传递数据。当子组件中的某些操作需要通知父组件并传递相关数据时,就可以使用
defineEmits
来定义事件,通过触发这些事件来实现数据的向上传递。
- 用于子组件向父组件传递数据。当子组件中的某些操作需要通知父组件并传递相关数据时,就可以使用
- 实例
- 子组件(
ChildComponent.vue
)<template> <div> <button @click="sendData">向父组件发送数据</button> </div> </template> <script setup> import { defineEmits } from 'vue'; const emits = defineEmits(['childData']); const sendData = () => { const data = '这是子组件发送的数据'; emits('childData', data); }; </script>
- 父组件(
ParentComponent.vue
)<template> <div> <ChildComponent @childData="receiveData" /> </div> </template> <script setup> import ChildComponent from './ChildComponent.vue'; const receiveData = (data) => { console.log('接收到子组件的数据:', data); }; </script>
- 子组件中,首先使用
defineEmits
定义了一个名为childData
的事件。然后在sendData
方法中,创建了要发送的数据data
,并通过emits('childData', data)
触发childData
事件,将数据发送给父组件。父组件通过@childData="receiveData"
监听子组件的childData
事件,并在receiveData
方法中接收和处理数据。
- 子组件(
- 应用场景
-
defineExpose
- 应用场景
- 当我们使用
<script setup>
语法时,组件默认是封闭的,外部无法访问组件内部的属性和方法。defineExpose
可以用于有选择地将组件内部的属性和方法暴露给外部,例如在父组件通过模板引用(ref
)获取子组件实例并访问其内部属性或方法时使用。
- 当我们使用
- 实例
- 子组件(
ExposedComponent.vue
)<template> <div> <p>{{ internalData }}</p> </div> </template> <script setup> import { ref } from 'vue'; const internalData = ref('这是内部数据'); const internalMethod = () => { console.log('这是内部方法'); }; defineExpose({ internalData, internalMethod }); </script>
- 父组件(
ParentComponent.vue
)<template> <div> <ExposedComponent ref="childComponentRef" /> <button @click="accessChildDataAndMethod">访问子组件数据和方法</button> </div> </template> <script setup> import ExposedComponent from './ExposedComponent.vue'; import { ref } from 'vue'; const childComponentRef = ref(); const accessChildDataAndMethod = () => { console.log(childComponentRef.value.internalData.value); childComponentRef.value.internalMethod(); }; </script>
- 在子组件中,通过
defineExpose
将internalData
(一个响应式数据)和internalMethod
(一个方法)暴露给外部。父组件通过ref
获取子组件的引用,存储在childComponentRef
中。当点击按钮时,通过accessChildDataAndMethod
方法可以访问子组件暴露的internalData
和internalMethod
。
- 子组件(
- 应用场景
-
defineAsyncComponent
- 应用场景
- 用于异步加载组件,在大型应用中,如果一次性加载所有组件,可能会导致应用启动缓慢。
defineAsyncComponent
允许我们在需要的时候再加载组件,例如路由懒加载或者按需加载某些复杂的组件,这样可以提高应用的初始加载性能。
- 用于异步加载组件,在大型应用中,如果一次性加载所有组件,可能会导致应用启动缓慢。
- 实例
App.vue
(假设这是主应用组件)<template> <div> <button @click="loadComponent">加载异步组件</button> <component v - if="isComponentLoaded" :is="asyncComponent" /> </div> </template> <script setup> import { ref, defineAsyncComponent } from 'vue'; const isComponentLoaded = ref(false); const asyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue') ); const loadComponent = () => { isComponentLoaded.value = true; }; </script>
- 在这里,
defineAsyncComponent
接受一个函数,这个函数返回一个Promise
,用于异步加载./AsyncComponent.vue
组件。isComponentLoaded
是一个响应式数据,用于控制组件是否显示。当点击按钮触发loadComponent
方法时,isComponentLoaded
变为true
,此时异步加载的组件会被显示出来。
- 应用场景
-
defineOptions
- 应用场景
- 在Vue 3.5组件中,
defineOptions
主要用于在<script setup>
语法下更方便地定义组件选项,如组件名称、组件继承关系等。以往在<script setup>
中,这些选项可能需要通过一些额外的复杂方式来设置,defineOptions
提供了一种简洁的方式来处理。
- 在Vue 3.5组件中,
- 实例
MyComponent.vue
<template> <div> <h1>{{ componentName }}</h1> </div> </template> <script setup> import { ref, defineOptions } from 'vue'; const componentName = ref('My Awesome Component'); defineOptions({ name: 'MyCustomComponentName' }); </script>
- 在这个例子中,通过
defineOptions
定义了组件的名称为MyCustomComponentName
。在模板中,展示了一个响应式数据componentName
。这样可以方便地为组件指定一个自定义名称,在调试或者在其他地方引用组件时可能会用到这个名称。
- 在这个例子中,通过
- 应用场景
-
defineModel
- 应用场景
- 当需要在组件中创建一个双向绑定的数据接口时,
defineModel
非常有用。它通常用于自定义表单组件,使得组件内部的数据变化能够反馈到父组件,同时父组件的数据变化也能更新组件内部的数据。
- 当需要在组件中创建一个双向绑定的数据接口时,
- 实例
- 自定义输入组件(
CustomInput.vue
)<template> <input :value="modelValue" @input="updateModelValue" /> </template> <script setup> import { defineModel } from 'vue'; const model = defineModel(); const updateModelValue = (event) => { model.value = event.target.value; }; </script>
- 父组件(
App.vue
)<template> <div> <CustomInput v - model="inputValue" /> <p>父组件中的值: {{ inputValue }}</p> </div> </template> <script setup> import CustomInput from './CustomInput.vue'; const inputValue = ref('初始值'); </script>
- 在自定义输入组件中,
defineModel
创建了一个双向绑定的模型。通过model.value
可以获取和设置这个模型的值。在updateModelValue
方法中,当输入框的值发生变化(@input
事件触发)时,更新model.value
,这个变化会反馈到父组件。在父组件中,使用v - model="inputValue"
将inputValue
数据与自定义输入组件进行双向绑定,这样在组件内部或父组件中修改数据,另一方都会相应更新。
- 在自定义输入组件中,
- 自定义输入组件(
- 应用场景