一、典型的Vite+Vue3项目结构
续上文成功创建Vue3项目的脚手架,通过visual Studio Code软件打开刚刚创建的文件夹,将会看到这样一个项目结构。
使用Vite构建Vue3项目时,项目结构通常遵循一定的组织规则,以保持代码的清晰和可维护性。以下是一个典型的Vite+Vue3项目结构分析: (部分省略)
补充说明:
①'index.html': 项目的入口HTML文件,其中包含一些基本的HTML结构和用于挂载Vue应用的‘’<div id="app"></div>‘’元素。
②router': 路由配置目录,通常包含一个`index.ts`文件,用于设置路由规则。
③' store': 状态管理目录,通常包含一个`index.ts`文件,用于配置Vuex或Pinia等状态管理库。
二、单组件开发
1、概述:
单组件开发是指在前端框架Vue.js中,将一个Vue组件的模板、逻辑和样式封装在一个单独的文件中进行开发的方式。这种文件通常以'.vue'为后缀,因此也常被称为单文件组件(Single File Component,简称SFC)。
2、在单组件开发中,一个组件通常包含以下三个部分:
(1) <template>:
这部分定义了的HTML结构,也就是组件的外观。
(2) '<script>':
这部分包含了组件的JavaScript逻辑,定义了组件的行为和数据。
(3)'<style>':
这部分写明了组件的样式。通过添加`scoped`属性,可以确保这些样式仅应用于当前组件,避免样式冲突。
3、代码示例:
<script setup>import { reactive } from "vue";import Header from "./components/Header.vue";import Footer from "./components/Footer.vue";import Nav from "./components/Nav.vue";const propStudent = {name: "李雷",age: 19, }const propTeacher = reactive( {name: "韩梅梅",age: 25, })const addTeacherAge = () => {propTeacher.age ++ console.log(`教师年龄为${propTeacher.age}`); }
</script><template><!-- 实验一: 在父组件里,设置子组件的“字符串型”属性(进而用于父组件向子组件传递数据) --><Header propName="王五" propAge="21" /><!-- 实验二: 在父组件里,设置子组件的“对象型”属性(进而用于父组件向子组件传递数据)--><Nav v-bind="propStudent" /><!-- 实验二: 在父组件里,设置子组件的“响应型”属性(进而用于父组件向子组件传递数据) --><Footer v-bind="propTeacher" /><button v-on:click="addTeacherAge">在父组件中增加教师年龄</button>
</template><style scoped></style>
三、父组件向子组件传递数据
1、概述:
父组件向子组件传递数据是组件化编程中的一种常见操作,它允许父组件将与子组件有关的信息或数据通过特定的属性(称为props)传递给子组件。这种方式保证了组件之间的数据传递是清晰定义和可维护的,同时也遵循了单向数据流的设计原则,即数据总是从父组件流向子组件,子组件不允许直接修改这些props的值,而是通过事件向上反馈信息,由父组件来决定是否及如何更新数据。
2、父组件向子组件传递数据的过程通常涉及以下步骤:
(1)定义Props: 父组件在其子组件标签上定义一些属性,并将数据作为这些属性的值传递。
(2)接收Props: 子组件在其定义中声明接收这些属性,并在其内部通过'props'对象访问这些数据。
3、示例1:
(1)父组件(App.vue)
<!-- 父组件的模板 -->
<template><div><child-component :message="helloMessage" /></div>
</template><script>
import ChildComponent from './components/Header.vue';export default {components: {ChildComponent},data() {return {helloMessage: 'Hello from Parent!'};}
}
</script>
在上述代码中,':message="helloMessage"'是一个prop绑定,它将父组件中的'helloMessage'数据传递给'ChildComponent'。
(2)子组件(Header)
<!-- 子的模板 -->
<template><div><p>{{ message }}</p></div>
</template><script>
export default {props: ['message']
}
</script>
在子组件中,'props: ['message']'声明了子组件期望从父组件接收一个名为'message'的prop,然后可以在模板中使用这个prop显示数据。
运行结果:
4、示例2
(1)父组件(App.vue)
<!-- 父组件的模板 -->
<template><div><Header :propName="userName" :propAge="userAge" /></div>
</template>
<script setup>
import Header from './components/Header.vue';
const userName = '王晓';
const userAge = 19;
</script>
注释:
①'<template>' 标签:这是Vue组件的模板部分,它定义了组件的结构和HTML标记。
② '<Header :propName="userName" :propAge="userAge" />':这行代码是在父组件中使用了子组件'Header'。'Header'组件通过props(属性)接收数据。这里有两个属性绑定:
':propName="userName"':这是一个prop绑定,'propName'是子组件'Header'中定义的一个prop名称,它被绑定到父组件中的'userName'变量。这里的`userName`的值是字符串`'王晓'`。
':propuserAge"':这是另一个prop绑定,'propAge'是子组件'Header'中的另一个prop名称,它被绑定到父组件中的'userAge'变量。这里的'userAge'的值是数字'19'。③'import Header from'./components/Header.vue';':这行代码导入了名为'Header'的子组件,它位于当前目录下的'components'文件夹中,并且文件名为'Header.vue'。
④ 'const userName = '王晓';'和'const userAge = 19;':这两行代码定义了一个名为'userName'和'userAge'的常量,并赋值为字符串“王晓”和数字'19'。
(2)子组件(Header.vue)
<!-- 子的模板 -->
<template><div><h3>示例2</h3>
<p>{{propName}}</p >
<p>{{propAge}}</p ></div>
</template>
<script setup>const props = defineProps(["propName", "propAge"])console.log(props);
</script>
<style scoped>div {width:25%;}
</style>
注释:
①<p>{{propName}}</p>和<p>{{propAge}}</p>是段落元素,其中的{{propName}}和{{propAge}}是一个绑定表达式,用于显示传递给组件的propName属性的值和用于显示传递给组件的propAge属性的值
②const props = defineProps(["propName", "propAge"]) : 使用defineProps定义了组件的两个属性:propName和propAge。 console.log(props): 在控制台打印出传递给组件的props对象,这将包含propName和propAge属性及其值
③这个组件在接收了propName和propAge后,会将这些值显示在页面上,并在控制台输出这些属性的当前值。
运行结果:
控制台:
四、子组件向父组件传递数据
1、概述:
在Vue中,子组件向父组件传递数据通常是通过自定义事件来实现的。这种机制允许子组件在其内部状态发生变化时通知父组件,父组件可以监听这些事件并响应。
2、实现步骤:
(1) 父组件在引用子组件的时候,绑定一个自定义事件监听器。
(2)子组件通过'$emit'方法触发这个自定义事件,并将需要传递的数据作为参数传递给父组件。
(3)父组件在自定义事件的监听器中接收这些数据,并可以根据这些数据更新自身的状态。
3、子组件(Header.vue)
<template><div><button @click="sendMessageToParent">发送数据到父组件</button></div>
</template><script setup>
import { defineEmits } from 'vue';// 定义一个自定义事件及其可以接收的参数类型
const emit = defineEmits(['messageFromHeader']);// 方法用于在点击按钮时触发自定义事件,并传递数据
function sendMessageToParent() {const message = 'Hello from Header!';emit('messageFromHeader', message);
}
</script>
注释:
(1)'<template>'部分定义了子组件的HTML结构。包含了一个按钮'<button>',用户点击这个按钮将会发送数据到父组件。
(2)'<script setup>' 部分定义了子组件的逻辑。
① 引入了'defineEmits'函数,用于定义组件可以触发的事件以及这些事件可以接收的参数类型。
②使用'defineEmits'定义了一个名为'messageFromHeader'的自定义事件。
③ 定义了一个名为'sendMessageToParent'的函数,当按钮被点击时触发。函数内部创建了一个字符串'message',并通过'emit'函数触发'messageFromHeader'事件,将字符串作为参数传递给父组件。
4.父组件(App.vue)
<template><div><Header @messageFromHeader="handleMessageFromHeader" /><p>从子组件接收到的消息:{{ message }}</p></div>
</template><script setup>
import { ref } from 'vue';
import Header from './components/Header.vue';// 用于接收子组件传递的数据
const message = ref('');// 事件处理器,用于处理子组件发来的消息
function handleMessageFromHeader(msg) {message.value = msg;
}
</script>
注释:
(1)'<template>' 部分定义了组件的HTML结构。
- 包含了一个'<Header>'组件的标签,用于引入子组件。
- 有一个段落'<p>'用于显示从子组件接收到的消息。
- 使用了'{{ message }}'来绑定父组件中的'message'数据,以便动态显示。2. '<script setup>'部分,用于定义组件的逻辑。
- 引入了'ref'函数,它用于创建响应式数据。
- 引入了'Header'组件,它位于'./components/Header.vue'路径下。
- 定义了一个名为'message'的响应式数据,初始值为空字符串。
- 定义了一个名为'handleMessageFromHeader'的事件处理器函数,它用于接收子组件传递的数据。函数将接收到的消息'msg'赋值给'message',从而更新显示的内容。
运行结果:
小结实现步骤:
1. 父组件通过('<Header>')标签引入子组件,并监听子组件发出的('messageFromHeader')事件。
2. 父组件定义了一个事件处理器('handleMessageFromHeader')来接收从子组件传递的数据,并将其存储在响应式数据('message')中。
3. 子组件定义了一个按钮,当用户点击按钮时,执行('sendMessageToParent')函数。
4. ('sendMessageToParent')函数通过('emit')函数触发('messageFromHeader')事件,并将要发送的消息作为参数传递。
5. 当父组件监听到('messageFromHeader')事件时,执行('handleMessageFromHeader')函数,更新显示从子组件接收到的消息。
五、跨组件通信
1、概述:
在Vue框架中,跨组件通信指的是组件之间进行数据和方法传递的过程,特别是当这些组件不是简单的父子关系时。Vue中的跨组件通信是为了在组件之间共享数据和状态,实现复杂的交互逻辑,从而构建更加灵活和可维护的前端应用程序。
2、常见的跨组件通信方式
(1) Props 和 Events
这是 Vue 中最常用的父子组件通信方式 。 父组件通过 props 向子组件传递数据 , 子组件则可以通过自定义事件 ( $ emit ) 向父组件发送消息 。代码示例:
①App.vue部分
<template><div><h1>父组件</h1><Headercomponents :some-prop="message" @some-event="handleEvent" /></div>
</template><script>
import Headercomponents from './components/Header.vue';export default {components: {Headercomponents},data() {return {message: 'Hello from Parent Component!'};},methods: {handleEvent(data) {console.log('Event received from Header:', data);}}
}
</script>
②Header.vue
<template><div><h2>子组件</h2><p>{{ someProp }}</p><button @click="sendMessageToParent">Send Message to Parent</button></div>
</template><script>
export default {props: {someProp: String},methods: {sendMessageToParent() {this.$emit('some-event', 'Hello from Header Component!');}}
}
</script>
注释:
在这个示例中:
- 父组件通过`<Headercomponents :some-prop="message" ...>`向子组件传递了一个名为`someProp`的prop,其值为`message`。
- 子组件在其`props`定义`中接收了`someProp`,并在模板中显示它。
- 子组件有一个按钮,当点击按钮时,会通过`this.$emit('some-event', 'Hello from Header Component!')`向父组件发送名为`some-event`的事件,并传递了一条消息。
- 父通过`@some-event="handleEvent"`监听这个事件,并在`handleEvent`方法中处理它,打印从子组件接收到的消息。
运行结果:
(2) Provide / Inject :
Vue 提供了一个 provide/inject 机制 , 允许祖先组件通过 provide 选项提供数据或方法 , 而任何后代组件都可以通过 inject 选项来接收这些数据或方法 , 无论组件层次结构有多深 。代码示例:
①App.vue部分
<script setup>import { provide, ref } from 'vue'import Header from "./components/Header.vue"/*provide用于某个上层组件将数据广播给所有下层组件。若使用了provide和inject来进行数据传递,则一般不需要再使用defineProps*///【实验1】某个上层组件向所有下层组件广播普通数据const web = {name:"百度一下", url:"www.baidu.com"}provide("provideWeb", web)//【实验2】某个上层组件向所有下层组件广播响应式数据const user = ref(0)provide("provideUser",user)//【实验3】某个上层组件向所有下层组件广播函数const userAdd = () => {user.value++} provide("provideFuncUserAdd",userAdd)
</script><template><h3>我是上层的App组件, 用户数为: {{ user }}</h3><Header/>
</template><style scoped></style>
②Nav.vue部分
<script setup>import { inject } from 'vue'//【实验1】下层组件通过inject注入上层App组件广播的普通数据const web = inject("provideWeb")console.log("provideWeb:", web)//【实验2】下层组件通过inject注入上层App组件广播的函数const funcUserAdd = inject("provideFuncUserAdd")console.log("provideFuncUserAdd:", funcUserAdd)//【实验3】下层组件通过inject注入上层Header组件广播的普通数据const url = inject("provideUrl")console.log("provideUrl:", url)
</script><template><h3>我是底层的Nav组件</h3><button @click="funcUserAdd">添加用户</button>
</template><style scoped></style>
③ Header.vue部分
<script setup>import { provide, inject } from 'vue'import Nav from "./Nav.vue"//【实验1】下层组件通过inject注入上层App组件广播的响应式数据const user = inject("provideUser")console.log("provideUser:", user.value)//【实验2】某个上层组件向所有下层组件广播函数const str = "www.xiaomi.com"provide("provideUrl", str)
</script><template><h3>我是中间的Header组件</h3><Nav/>
</template><style scoped></style>
六、插槽
1、概述:
在Vue中,插槽(Slots)是一种用于组合组件的机制,它允许父组件向子组件内部传递内容,而无需担心子组件的内部实现细节。简单来说,插槽就是子组件中用来嵌入父组件内容的占位符。
2、主要有以下几种类型:
(1)匿名插槽(默认插槽):
匿名插槽是插槽的基本形式,没有特定的名字。当父组件在子组件标签内部插入内容时,这些内容会被填充到子组件内部的匿名插槽中。
(2)具名插槽:
具名插槽具有特定的名字,父组件可以根据这个名字将内容插入到指定的位置。在一个子组件内部,可以通过`<slot>`标签定义具名插槽,并为其指定一个名字。
(3)作用域插槽:
作用域插槽允许父组件接收子组件传递的数据,并在父组件中可以使用这些数据。通常用于列表渲染等场景,使得父组件可以访问子组件的数据。
3、代码示例:
(1)匿名插槽(默认插槽):
①App.vue部分
<template><div><Card><p>这是自定义的内容(会被默认插入默认插槽)</p><!-- 【实验1】把代码片段插入默认插槽,可以不使用<template>标签封装 --><h3>插入默认插槽的三级标题</h3></Card></div>
</template><script setup>import Card from './components/Card.vue';
</script>
② Card.vue 部分
<template><div><!-- 定义一个默认插槽(不需要命名) --><div class="default-slot"><slot></slot></div></div>
</template><script setup></script><style>.default-slot{background-color: antiquewhite;}
</style>
运行结果:
(2)具名插槽:
①App.vue部分
<template><div><Card><!-- 【实验】把代码片段插入具名插槽,必须使用<template>标签封装,并且用v-slot:指定插槽的名字,v-slot:简写形式为# --><template v-slot:footer><button @click="handleClick">我是插入具名插槽的按钮</button></template></Card></div>
</template><script setup>import Card from './components/Card.vue';const handleClick = () => {alert('插入具名插槽的按钮被点击了!');};
</script>
② Card.vue 部分
<template><div><!-- 定义一个具名插槽(需要通过name命名,最常用) --> <div class="named-slot"><slot name="footer"></slot></div> </div>
</template><script setup></script><style>.named-slot{background-color: gainsboro;}
</style>
运行结果:
(3)作用域插槽:
①App.vue部分
<template><div><Card><!-- 【实验】作用域插槽:子组件将url和title数据配置到 name="user" 的插槽中,当父组件将某个片段插入该插槽时,便可通过 v-slot:user="data" (data是随便取的名字) 来接收这些数据--><template v-slot:user="data"> 子组件通过作用域插槽传来的数据: {{ data.url }} , {{ data.title }}</template> </Card></div>
</template><script setup>import Card from './components/Card.vue';const handleClick = () => {alert('插入具名插槽的按钮被点击了!');};
</script>
② Card.vue 部分
<template><div><!-- 定义一个作用域插槽(需要通过name命名,所以它也是具名插槽的一种,但要求在插槽里配置一些数据) --><div class="scoped-slot"><slot name="user" url="www.baidu.com" title="百度一下"></slot></div></div>
</template><script setup></script><style>.scoped-slot{background-color: pink;}
</style>
运行结果;