VueJS - 组件
VueJS - 组件
Section titled “VueJS - 组件”组件 (Components) 是构建 Vue(以及许多其他现代框架)应用的基础。它们允许你将 用户界面 (UI) 拆分成独立、可重用的部分,每个部分管理自己的 逻辑 (logic) 和 标记 (markup)。可以把它们想象成你可以在整个应用程序中定义和复用的自定义 HTML 元素。
在 Vue 3 中,组件通常定义为包含选项的对象 (Options API) 或使用 setup 函数 (Composition API)。为了更好的组织性,尤其是在 构建环境 (build environments) 中, 单文件组件 (Single-File Components, .vue 文件) 是定义组件的标准方式,它将 模板 (template)、脚本 (script) 和 样式 (style) 放在一起。
示例:基础组件(使用 Section titled “示例:基础组件(使用 )”
让我们在一个假设的 CounterButton.vue 文件中创建一个简单的计数器按钮组件:
<script setup>import { ref } from 'vue';
// Reactive state specific to this component instanceconst count = ref(0);
function increment() { count.value++;}</script>
<template> <button @click="increment"> You clicked me {{ count }} times </button></template>
<style scoped>/* Styles specific to this component */button { background-color: lightgreen; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;}button:hover { background-color: darkgreen; color: white;}</style><script setup> 是在 单文件组件 (SFC) 中使用 Composition API 的 编译时语法糖 (compile-time syntactic sugar)。在这里声明的变量和函数会自动暴露给模板。<style> 标签上的 scoped 属性确保 CSS 只应用于此组件内部的元素。
在使用组件之前,需要先注册它。
全局注册的组件可以在该 应用实例 (application instance) 内的任何组件模板中使用,无需单独导入。使用 app.component():
// main.js (Your application entry point)import { createApp } from 'vue';import App from './App.vue';import GlobalComponent from './components/GlobalComponent.vue';
const app = createApp(App);
// Register 'GlobalComponent' globallyapp.component('GlobalComponent', GlobalComponent);// Now <GlobalComponent /> can be used anywhere in App or its children
app.mount('#app');全局注册很方便,但如果注册了许多未使用的组件,可能会导致 打包体积 (bundle sizes) 较大。它通常更适合用于通用、广泛使用的组件(如基础按钮、输入框、模态框等)。
局部注册的组件仅在其注册的组件内部可用。对于大多数应用程序组件来说,这是首选方法,因为它使得依赖关系更加明确,并且更好地支持 摇树优化 (tree-shaking)(移除未使用的代码)。
在 <script setup> 中,只需导入组件即可使其在模板中可用:
<script setup>// Import the componentimport LocalButton from './LocalButton.vue';import AnotherComponent from './AnotherComponent.vue';</script>
<template> <div> <p>Using a locally registered component:</p> <!-- Use the imported component directly (PascalCase or kebab-case) --> <LocalButton /> <another-component></another-component> <!-- kebab-case also works --> </div></template>如果未使用 <script setup>(例如,使用 Options API),则需要使用 components 选项:
// ParentComponent.vue (Options API example)import LocalButton from './LocalButton.vue';
export default { components: { // Register the component locally LocalButton // You could also rename it: 'MyButton': LocalButton } // ... other options like data, methods}通过 Props 传递数据
Section titled “通过 Props 传递数据”组件通常需要接收来自父组件的数据。这是通过 props 来完成的。Props 是你可以在组件上注册的自定义属性/特性。父组件可以通过绑定到这些属性来将数据传递给子组件。
示例:带有 Prop 的问候组件
Section titled “示例:带有 Prop 的问候组件”<script setup>// Define the props the component accepts// 'defineProps' is a compiler macro, no import neededconst props = defineProps({ name: { type: String, // Type validation required: true, // Make the prop required default: 'World' // Default value if not provided (though 'required' makes this less relevant) }});
// Props are accessible directly in the template,// or via the 'props' object in script setupconsole.log('Greeting received name:', props.name);</script>
<template> <h2>Hello, {{ name }}!</h2></template>defineProps 是一个 编译器宏 (compiler macro),无需导入。Props 在模板中可以直接访问,或者在 <script setup> 中通过 props 对象访问。
在父组件中使用 Greeting 组件:
<script setup>import { ref } from 'vue';import Greeting from './Greeting.vue';
const userName = ref('Alice');</script>
<template> <div> <!-- Pass static value --> <Greeting name="Bob" />
<!-- Bind to dynamic value using v-bind (or :) --> <Greeting :name="userName" />
<input v-model="userName" placeholder="Change name"/> </div></template>Props 允许 单向数据流 (one-way data flow):父组件将数据传递给子组件。子组件不应该直接修改 props。
触发事件 (Emitting Events):子组件到父组件通信
Section titled “触发事件 (Emitting Events):子组件到父组件通信”由于子组件不应该通过 props 直接修改父组件的状态,它们通过 触发自定义事件 来与父组件通信。父组件使用 v-on(或 @)监听这些事件。
示例:触发 ‘update’ 事件的自定义输入组件
Section titled “示例:触发 ‘update’ 事件的自定义输入组件”<script setup>// Define props (e.g., modelValue for v-model compatibility)const props = defineProps(['modelValue']);
// Define the events the component can emit// 'defineEmits' is a compiler macroconst emit = defineEmits(['update:modelValue', 'focused']);
function handleInput(event) { // Emit the 'update:modelValue' event with the new value // This pattern allows the component to work with v-model emit('update:modelValue', event.target.value);}
function handleFocus() { emit('focused'); // Emit a custom 'focused' event}</script>
<template> <input type="text" :value="modelValue" @input="handleInput" @focus="handleFocus" placeholder="Custom Input" /></template>defineProps 和 defineEmits 都是 编译器宏 (compiler macro),无需导入。
在父组件中使用 CustomInput:
<script setup>import { ref } from 'vue';import CustomInput from './CustomInput.vue';
const textValue = ref('');
function onInputFocus() { console.log('Custom input was focused!');}</script>
<template> <div> <p>Current Value: {{ textValue }}</p>
<!-- Use v-model (which listens for 'update:modelValue' event) --> <CustomInput v-model="textValue" @focused="onInputFocus" />
<!-- Explicitly listening (equivalent to v-model): --> <!-- <CustomInput :modelValue="textValue" @update:modelValue="newValue => textValue = newValue" @focused="onInputFocus" /> --> </div></template>子组件触发事件(update:modelValue、focused),父组件监听(v-model、@focused)以进行相应的响应。
有时你需要动态切换不同的组件。Vue 提供了带有 is 特殊属性的 <component> 元素来实现这一目的。
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Vue 3 Dynamic Components</title> <script src="https://unpkg.com/vue@3"></script> <style> .tab-button { padding: 6px 10px; border-bottom: 2px solid #ccc; cursor: pointer; background: #f0f0f0; margin-right: -1px;} .tab-button:hover { background: #e0e0e0; } .tab-button.active { background: #e0e0e0; border-bottom-color: green; } .tab-content { border: 1px solid #ccc; padding: 10px; } </style></head><body> <div id="app"> <button v-for="tab in tabs" :key="tab.id" :class="['tab-button', { active: currentTab === tab.component }]" @click="currentTab = tab.component" > {{ tab.name }} </button>
<!-- Dynamic component is rendered here --> <div class="tab-content"> <!-- Use keep-alive to preserve state when switching --> <keep-alive> <component :is="currentTabComponent"></component> </keep-alive> </div> </div>
<script> const { createApp, ref, shallowRef, computed } = Vue;
// Define some simple components (could be imported from .vue files) const TabHome = { template: '<p>Home component content</p>' }; const TabPosts = { template: '<p>Posts component content</p>' }; const TabArchive = { template: '<p>Archive component content</p>' };
const app = createApp({ setup() { const tabs = shallowRef([ // Use shallowRef for non-deep reactivity on the array itself { id: 1, name: 'Home', component: 'TabHome' }, { id: 2, name: 'Posts', component: 'TabPosts' }, { id: 3, name: 'Archive', component: 'TabArchive' } ]); const currentTab = ref('TabHome'); // Holds the *name* of the current component
// Computed property to resolve the actual component object const currentTabComponent = computed(() => { switch (currentTab.value) { case 'TabHome': return TabHome; case 'TabPosts': return TabPosts; case 'TabArchive': return TabArchive; default: return TabHome; } });
return { tabs, currentTab, currentTabComponent }; } });
// Register components (globally for this simple example) app.component('TabHome', TabHome); app.component('TabPosts', TabPosts); app.component('TabArchive', TabArchive);
app.mount('#app'); </script></body></html>在这个选项卡式界面的例子中:
currentTabref 存储要显示的组件的名称。- 计算属性
currentTabComponent将组件名称解析为实际的组件对象。 <component :is="currentTabComponent">渲染由计算属性决定的组件。- 点击按钮改变
currentTab,从而更新currentTabComponent,导致<component>元素渲染新的组件。 <keep-alive>用于缓存不活动的组件,以便在来回切换时保留它们的状态。
组件是构建可扩展和可维护的 Vue 应用程序的基础。掌握 props、事件和 组件组合 (component composition) 是精通 Vue 的关键。
进一步阅读:Vue 官方文档中关于组件基础的章节:https://vuejs.org/guide/essentials/component-basics.html 和组件深入的章节:https://vuejs.org/guide/components/registration.html