vueVite
本文讲述使用Vite搭建Vue3项目的结构及其作用
项目结构及其作用
- 根目录:
- package.json:这个文件包含了项目的依赖项、脚本等信息,是整个项目的元数据。
- vue.config.js:这是用于配置 Vite 的主要文件。在这里,你可以定义环境变量、设置插件、调整打包选项等。这是一个非常重要的配置文件,它会影响到你的开发体验和生产部署过程
- public:包含了一些静态资源,比如图标、favicon.ico、robots.txt 等。这些文件会被直接复制到构建目录中,不会经过 Vite 的处理。
- 在组件中导入public中的静态资源时不需要完整的路径,直接/文件名即可比如
import qrImg from '/qr.png'
;导入public中的二维码图片
- 在组件中导入public中的静态资源时不需要完整的路径,直接/文件名即可比如
- src 文件夹:在 Vite 中最核心的一个文件夹,因为它存放着所有的源代码: index.htm-main.js-index.js、App.vue-其他view、components组件-静态资源(图片、cdn等等)
- assets 文件夹:用来存储静态资源,如图片、样式表等,这些资源通常不会经常变化,可以直接复制到服务器上
- components 文件夹:存放的是你写好的复用组件,每个子文件夹代表一类相关的组件,比如说有一个 base 或者 app 组件,然后再根据具体功能划分出不同的模块(例如:button、table 等)。在vue项目中可以将其中的文件称为vue文件:封装和组织组件的模板、逻辑和样式,使得组件的开发和管理更加高效和模块化(往往在components下创造组件,在view下的vue文件中引用组件),提高了代码的可维护性和复用性:其基础结构如下:
<template></template>
:内含html代码,用于组成页面结构- 浏览器中不会存在
<template>
标签的原因是,Vue会在编译过程中将<template>
中的内容转换为JavaScript渲染函数。在这个过程中,Vue会解析<template>
的内容并生成虚拟DOM,而这个虚拟DOM最终会被渲染到实际的DOM中。 - 编译过程:
- 解析模板:Vue使用vue-template-compiler库将
<template>
块中的HTML字符串解析为抽象语法树(AST),这表示了模板的结构。 - 生成渲染函数:AST会被转换为JavaScript代码,这些代码负责创建虚拟DOM节点。每当组件需要渲染时,Vue会调用这些渲染函数,而不是直接使用
<template>
中的内容。 - 更新DOM:当数据变化时,Vue会根据虚拟DOM的变化来更新实际的DOM。
- 解析模板:Vue使用vue-template-compiler库将
- 浏览器中不会存在
调用组件
- 引入组件有两种方式
- 直接在要使用的页面中导入
- 在main.js中导入,然后app.component(”组件名称”,组件)注入全局这样就可以在全局使用了
- 在组件中调用的组件名称也有两种方式
在 Vue.js 中,当您在main.js
中全局注册组件时,例如:
1
2
3
4
5
6
7
8// main.js
import { createApp } from 'vue';
import App from './App.vue';
import PanelHead from './components/PanelHead.vue';
const app = createApp(App);
app.component('PanelHead', PanelHead);
app.mount('#app');尽管您注册的组件名称是
'PanelHead'
,但在模板中可以使用<panel-head></panel-head>
或<PanelHead></PanelHead>
的形式来引用,原因如下:
组件名的大小写不敏感解析:
Vue 在解析模板时,会将自定义组件标签名中的连字符形式(kebab-case)和大驼峰形式(PascalCase)都识别为同一个组件。例如:
<PanelHead></PanelHead>
<panel-head></panel-head>
上述两种写法都会被解析为注册的
'PanelHead'
组件。组件名的规范化:
根据 Vue 的组件名解析规则,注册时的组件名会被标准化处理,模板中的标签名也会被转换为相应的格式,以进行匹配。
- 注册组件时使用 PascalCase(大驼峰命名)的名称。
- 在模板中,可以使用 PascalCase 或 kebab-case(短横线命名)形式的组件标签。
HTML 中的大小写不敏感性:
- 在浏览器解析 HTML 时,标签名是大小写不敏感的。
- 为了遵循 HTML 规范,Vue 推荐在模板中使用 kebab-case 的组件名。
总结:
- 注册组件: 使用 PascalCase 命名,例如
'PanelHead'
。 - 在模板中引用: 可以使用
<PanelHead></PanelHead>
或<panel-head></panel-head>
,Vue 都能够正确解析。 - 推荐使用: 在模板中使用 kebab-case 形式,即
<panel-head></panel-head>
,以符合 HTML 规范和提高可读性。
- 引入组件有两种方式
组合式API与选项式API的对比(本项目中使用组合式API)
组合式API:Vue3提供了丰富的组合式API,帮助开发者管理组件的行为,核心功能如下:
<script setup>
是在单文件组件中使用组合式 API 的编译时语法糖。主要特点:顶层变量/函数自动暴露给模板:意思是说
<script setup>
中定义的变量和函数会自动暴露给当前组件的模板使用;这种暴露仅限于当前组件内部的 template 部分;- 无需 return 和 export 语句
- 引入的组件自动注册
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const count = ref(0)
const increment = () => count.value++
</script>
<template>
<div>
<p>{{ count }}</p>
<button @click="increment">+1</button>
<ChildComponent />
</div>
</template>
```
2. 组件实例的访问:父组件通过 ref 访问子组件时,访问的是子组件的实例;出于安全考虑,Vue 3 默认关闭了组件实例的属性访问需要通过 defineExpose 明确声明哪些属性/方法可以被父组件访问;
```js
<!-- 子组件 Child.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0) // ✅ 自动暴露给当前模板使用
const increment = () => count.value++
defineExpose({ count }) // ✅ 显式暴露给父组件访问
</script>
<template>
<div>
{{ count }} <!-- ✅ 可以直接访问 count -->
<button @click="increment">+1</button> <!-- ✅ 可以直接访问 increment -->
</div>
</template>
<!-- 父组件 Parent.vue -->
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
// 访问子组件实例的属性
console.log(childRef.value.count) // ✅ 可以访问(因为被 defineExpose 暴露)
childRef.value.increment // ❌ 不能访问(未被暴露)
</script>
<template>
<Child ref="childRef" />
</template>
这种设计的目的是:
提高代码的可维护性 增强组件的封装性 避免父组件随意访问和修改子组件的内部状态
- 响应式系统:数据变更自动触发视图更新
1
2
3
4
5
6
7
8
9
10
11
12import { ref, reactive } from 'vue'
// ref 基本类型响应式
const count = ref(0)
count.value++ // 修改值会触发视图更新
// reactive 对象响应式
const state = reactive({
name: '张三',
age: 25
})
state.age++ // 修改值会触发视图更新 - 生命周期钩子(选项式 API:Vue 通过组件选项自动处理逻辑,无需手动导入响应式函数和生命周期钩子。组合式 API:需要手动导入所需的响应式函数和生命周期钩子,从而提供更灵活和模块化的代码结构,他们俩周期钩子也有差别;比如组合式没有beforecreate,把setup当created用,其它就当改了个名)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42<template>
<div>{{ count }}</div>
<button @click="onClick">
增加 1
</button>
</template>
<script setup>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue';
const count = ref(1);
const onClick = () => {
count.value += 1;
};
onBeforeMount(() => {
alert("组件渲染之前调用的时beforeMount函数");
});
onMounted(() => {
alert("组件渲染完成调用的时mounted函数");
});
onBeforeUpdate(() => {
alert("组件更新之前调用的时beforeUpdate函数");
});
onUpdated(() => {
alert("组件更新之后调用的时updated函数");
});
onBeforeUnmount(() => {
alert("组件卸载之前调用的时beforeUnmount函数");
});
onUnmounted(() => {
alert("组件卸载之后调用的时unmounted函数");
});
</script>
4. 响应式监听
1. watch的两个参数
1. watch 的第一个参数需要是一个响应式数据(可以是多个),或者是一个返回值的函数。(比如返回一个对象obj的属性() => obj.age)
2. 箭头函数(newValue,oldValue) => {}
1. oldValue:监听的值在变化之前的值
2. newValue:监听的值在变化之后的值
2. watch的触发条件
1. 要让 watch 监听器生效,需要在运行时修改被监听的响应式数据。直接在代码中修改变量的初始值(即在组件加载前设置的值)并不会触发 watch 的回调函数。watch 的作用是监听响应式数据的变化,当数据在运行时发生改变时(例如用户点击按钮触发事件函数),watch 才会检测到这种变化并执行回调函数。
3. watch的两个属性:
1. immediate(获取初始化):当设置为 true 时,监听器会在绑定后立即触发回调函数。这意味着在初始化阶段,即使被监听的值没有发生变化,回调函数也会被调用一次。作用如下
1. 在组件加载时,需要根据初始值执行一些操作,例如根据初始参数请求数据。而不必等待被监听的值发生变化。
2. 在组件创建时,需要将某些响应式数据同步到其他地方
3. 在初始化组件的时候oldValue是undefined,newValue是当前值(初始值)
2. deep:true
1. Vue3 的 reactive 会自动对对象进行深层代理(Proxy),所以:当直接修改 监听对象的内置嵌套对象时,两个监听器都会触发
2. deep: true 主要用于以下场景:
1. 当你使用 reactive 对象作为 watch 的源,且需要在对象被整个替换时触发监听
2. 当监听的是一个返回非响应式对象的 getter 函数时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26const deep = reactive({
name: 'JJ',
age: 19,
info: {
address: 'Beijing',
contacts: {
phone: '123456',
email: 'test@example.com'
}
}
})
watch(deep, () => {
console.log('obj changed')
})//vue3自带不需要deep
// 如果是这种情况,则需要 deep: true:当改变的是 deep.info时,returnNonReactiveObj会被整个替换
const returnNonReactiveObj = () => ({
info: deep.info
})
watch(returnNonReactiveObj, (newValue, oldValue) => {
console.log('non-reactive obj changed:', newValue)
}, { deep: true })
</script>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29<script setup>
import { ref, reactive, watch } from 'vue'
const counter1 = ref(0)
const counter2 = ref(0)
// 监听多个
watch([counter1, counter2], (newValue, oldValue) => {
console.log('The new counter1 value is: ' + counter1.value)
console.log('The new counter2 value is: ' + counter2.value)
})
const obj = reactive({
name: 'JJ',
age: 18
})
//监听初始化值
watch(obj, (newValue, oldValue) => {
console.log('The new obj value is: ' + obj.value)
}, {
immediate: true
})
// watch监听单个属性
watch(() => obj.name, (newValue, oldValue) => {
console.log('The new obj value is: ' + obj.value)
}, {
immediate: true
})
</script>计算属性:更加灵活,可以在定义响应式变量时声明;
computed
可以用于任何类型的数据处理,不仅限于数值计算。- 核心优势
- 可处理任何数据类型
- 自动跟踪依赖关系
- 具有缓存机制
- 提高代码可读性和维护性
- 以下是一些常见用例
字符串处理
1
2
3// 字符串转换
const upperCase = computed(() => name.value.toUpperCase())
const fullName = computed(() => `${firstName.value} ${lastName.value}`)数组处理
1
2
3
4// 数组过滤
const activeTodos = computed(() => todos.value.filter(todo => !todo.completed))
// 数组排序
const sortedList = computed(() => items.value.sort((a, b) => a.id - b.id))对象处理
1
2
3
4
5
6// 对象转换
const userInfo = computed(() => ({
fullName: `${user.value.firstName} ${user.value.lastName}`,
age: user.value.age,
isAdult: user.value.age >= 18
}))复杂逻辑
1
2
3
4
5
6// 购物车计算
const cartTotal = computed(() => {
return cart.value.reduce((total, item) => {
return total + (item.price * item.quantity)
}, 0)
})字符倒序
1
2
3
4
5
6
7
8
9
10
const value = ref('this is a value')
// 注意这里
const reversedValue = computed(() => {
// 使用 ref 需要 .value
return value.value
.split('').reverse().join('');
})
- 核心优势
条件渲染和列表渲染
v-for的参数
- 如下代码中的v-for=”(item, index) in props.menuData” 中:这个 index 是 Vue 提供的数组索引值,是 v-for 指令内置提供的第二个参数表示当前遍历项在数组中的索引值(从0开始)是可选参数,如果不需要索引值,可以省略
1
2
3
4
5
6
7
8
9
10!-- 假设 props.menuData 是这样的数组 -->
[
{ meta: { id: 1, name: '菜单1' } }, // index = 0
{ meta: { id: 2, name: '菜单2' } }, // index = 1
{ meta: { id: 3, name: '菜单3' } } // index = 2
]
v-for="(item, index) in array"
// item: 当前项
// index: 当前项的索引
- 如下代码中的v-for=”(item, index) in props.menuData” 中:这个 index 是 Vue 提供的数组索引值,是 v-for 指令内置提供的第二个参数表示当前遍历项在数组中的索引值(从0开始)是可选参数,如果不需要索引值,可以省略
v-if 中 index 和 key
index=”
${index}-${item.meta.id}
“- 这是 Element Plus 菜单组件特有的属性
- 用于标识每个菜单项的唯一路径
- 帮助菜单组件追踪当前选中的菜单项
- 通常用于控制菜单的激活状态
key
属性:1
:key="`${index}-${item.meta.id}`"
- 这是 Vue 框架要求的特殊属性
- 用于给 v-for 循环中的每个元素一个唯一标识
- 帮助 Vue 在虚拟 DOM 更新时准确识别节点
- 提高渲染性能和准确性
它们的主要区别:
index
是功能性的唯一标识,用于菜单组件的功能实现key
是结构性的唯一标识,用于 Vue 框架的 DOM 更新优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63<!-- 项目中的菜单组件 -->
<template>
<template v-for="(item, index) in props.menuData">
<!-- 无子菜单 -->
<el-menu-item
v-if="!item.children || item.children.length == 0"
:indx="`${props.index}-${item.meta.id}`"
:key="`${props.index}-${item.meta.id}`"
>
<!-- 结合路由信息以及动态元素实现图标渲染 -->
<el-icon size="20">
<component :is="item.meta.icon"></component>
</el-icon>
<!-- 菜单标题 -->
<span>{{item.meta.name}}</span>
</el-menu-item>
<!-- 有子菜单:分两个部分:标题+递归调用子菜单 -->
<el-sub-menu
v-else
:index="`${props.index}-${item.meta.id}`"
>
<template #title>
<!-- 标题区域的内容: #title是 Element Plus 的 el-sub-menu 组件预定义的插槽名称 -->
<el-icon size="20">
<component :is="item.meta.icon"></component>
</el-icon>
<span>{{item.meta.name}}</span>
</template>
<!-- 递归调用此组件实现子菜单 -->
<!-- index:1 1-2 1-3 实现每个菜单项的唯一标识 -->
<SubMenu
:index="`${props.index}-${item.meta.id}`"
:menuData="item.children"
/>
</el-sub-menu>
</template>
</template>
```
```js
<template>
<!-- 条件渲染 -->
<div v-if="show">显示内容</div>
<div v-else>其他内容</div>
<!-- 列表渲染 -->
<ul>
<li v-for="item in list" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(true)
const list = ref([
{ id: 1, name: '项目1' },
{ id: 2, name: '项目2' }
])
</script>v-if和v-show的区别
v-if
和v-show
都是 Vue.js 中用于条件渲染的指令,但它们有不同的作用和使用场景。总结:
- 使用
v-if
时,元素在条件为假时不会存在于 DOM 中。 - 使用
v-show
时,元素始终存在于 DOM 中,只是通过 CSS 控制显示或隐藏。
- 使用
v-if
- 作用:根据表达式的真假值来有条件地渲染元素。
- 特点:元素及其绑定的事件和子组件在条件为假时不会被渲染或销毁。
- 性能:初始渲染时有更高的开销,因为需要添加或删除 DOM 元素。
v-show
- 作用:根据表达式的真假值来切换元素的显示状态。
- 特点:元素始终会被渲染并保留在 DOM 中,只是通过 CSS 的
display
属性来控制显示或隐藏。 - 性能:初始渲染开销较小,但频繁切换显示状态时性能更好。
使用场景
- **
v-if
**:适用于在运行时条件很少改变的场景,因为它会在条件变化时进行 DOM 的添加和删除。 - **
v-show
**:适用于需要频繁切换显示状态的场景,因为它只会切换display
属性。
- **
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<template>
<!-- v-if 示例 -->
<div v-if="isShow">
这个元素会根据 isShow 的值来添加或删除
</div>
<!-- v-show 示例 -->
<div v-show="isVisible">
这个元素会根据 isVisible 的值来显示或隐藏
</div>
</template>
<script>
export default {
data() {
return {
isShow: true,
isVisible: true
};
}
};
</script>
事件处理与双向绑定
v-model
的用途:获取表单中的输入数据赋值给变量;v-model
是 Vue.js 中用于创建双向数据绑定的指令,主要用于表单输入元素与应用状态之间的同步。主要功能如下双向绑定:
- 自动将用户输入的值同步到组件的状态(如
data
或ref
)。 - 同时,当状态发生变化时,更新输入元素的显示值。
- 自动将用户输入的值同步到组件的状态(如
简化代码:
- 替代手动编写
:value
和@input
事件处理器,实现更简洁的双向绑定。
- 替代手动编写
- 常见用法
文本输入:
1
<input v-model="变量名" />
复选框:
1
<input type="checkbox" v-model="变量名" />
- 选择框:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27<select v-model="变量名">
<option value="A">A</option>
<option value="B">B</option>
</select>
```js
<template>
<input v-model="username" />
<button @click="handleClick">点击</button>
</template>
<script setup>
import { ref } from 'vue'
const username = ref('')
const handleClick = () => {
console.log(username.value)
}
</script>
/*
- **`v-model="username"`**:
- 将输入框的值与 `username` 变量绑定。
- 用户在输入框中输入内容时,`username` 会自动更新。
- 如果在代码中修改 `username` 的值,输入框的显示内容也会相应更新。
*/
对比一下选项式API:在 Vue 2 和 Vue 3 的选项式 API 中的
<script>
中需要 return 和 export 的原因:(这些在组合式API中都不用考虑)- export default 的目的:
- 将组件配置对象导出,使其可以被其他组件引入使用
- 这是 ES6 模块系统的要求
- Vue 通过这个导出的对象来创建组件实例
- return 的目的:
- 在 data() 函数中返回数据对象,使其成为响应式数据
- 每个组件实例都需要独立的数据副本,避免数据共享
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31<script>
// 选项式 API
export default {
name: 'MyComponent',
// data 必须是函数并返回对象,确保每个组件实例有独立的数据副本
data() {
return {
count: 0,
message: 'Hello'
}
},
methods: {
increment() {
this.count++
}
},
computed: {
doubleCount() {
return this.count * 2
}
}
}
</script>
// ❌ 错误示例
data: {
count: 0
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34//组合式API中:组合式API基础语法
<script setup>
import { ref, reactive } from 'vue'
// 方式1:使用 ref() 定义基础类型
const count = ref(0)
const message = ref('Hello')
// 方式2:使用 reactive() 定义对象类型
const state = reactive({
count: 0,
message: 'Hello'
})
// 使用时:(在template之外)
// ref 需要 .value
console.log(count.value)
console.log(message.value)
// reactive 直接使用
console.log(state.count)
console.log(state.message)
</script>
<template>
<!-- 在模板中使用 ref 不需要 .value -->
<div>{{ count }}</div>
<div>{{ message }}</div>
<!-- reactive 对象的使用 -->
<div>{{ state.count }}</div>
<div>{{ state.message }}</div>
</template>
- export default 的目的:
选项式与组合式API的区别(除了上述之外):
选项式 API:Vue 通过组件选项自动处理逻辑,无需手动导入响应式函数和生命周期钩子等等。
组合式 API:需要手动导入所需的响应式函数和生命周期钩子等等,从而提供更灵活和模块化的代码结构。(除了编译器宏比如defineProps,编译器宏:由 Vue 的编译器在编译时处理的特殊语法糖。它并非普通的 JavaScript 函数,因此不需要通过 import 从 ‘vue’ 中导入。自动可用:在
<script setup>
块内,defineProps 会被自动识别并处理。)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33//拿子传父举例
// 选项式 API
<template>
<button @click="sendMessage">Send Message</button>
</template>
<script>
export default {
methods: {
sendMessage() {
this.$emit('message-event', 'Hello from child');//自动处理this.$emit
}
}
};
</script>
// 组合式 API
<template>
<ChildComponent @message-event="handleMessageEvent" />
<p>{{ messageFromChild }}</p>
</template>
<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue'
// 必须遵循js的规范,任何变量使用前必须申明
let messageFromChild = ref('');
const handleMessageEvent = (message) => {
messageFromChild.value = message;
};
</script>选项式 API 和 组合式 API 的主要区别补充:
组织代码的方式
- 选项式 API:通过选项(如
data
、methods
、computed
等)组织代码,适合简单组件。 - 组合式 API:通过组合函数(如
setup
)组织代码,更适合复杂逻辑和代码复用。
- 选项式 API:通过选项(如
逻辑复用
- 选项式 API:依赖混入(mixins)和高阶组件(HOCs),可能导致命名冲突和难以追踪。
- 组合式 API:使用组合函数(composables)实现逻辑复用,结构更清晰,避免命名冲突。
类型推导和 TypeScript 支持
- 选项式 API:TypeScript 支持有限,类型推导较复杂。
- 组合式 API:与 TypeScript 集成更好,提供更精准的类型推导。
代码组织与可维护性
- 选项式 API:按选项分割,功能分散,难以管理大型组件。
- 组合式 API:按功能分割,相关逻辑集中,提升可维护性。
生命周期钩子
- 选项式 API:通过选项直接定义生命周期钩子(如
mounted
、created
)。 - 组合式 API:在
setup
内使用函数(如onMounted
、onCreated
)定义生命周期钩子。
- 选项式 API:通过选项直接定义生命周期钩子(如
响应式系统
- 选项式 API:使用
data
对象进行响应式管理,自动处理响应式属性。 - 组合式 API:使用
ref
、reactive
等函数显式创建响应式数据,更灵活。
- 选项式 API:使用
学习曲线
- 选项式 API:更直观,适合 Vue 新手。
- 组合式 API:需要理解响应式原理和组合函数,学习曲线稍陡。
- 调试和测试
- 选项式 API:由于逻辑分散,单独测试某一功能较困难。
- 组合式 API:逻辑集中,单元测试和调试更容易。
最后总结一下两者的异同:
- 同:无论是选项式还是组合式API,都得遵循js的规范,比如任何变量使用前必须申明,否则会报错;调用其他组件时都需要导入相应文件;
- 异:
- 选项式需要export default导出组件对象,并在其中使用components申明导入的组件、data()函数return 返回数据变量/对象(定义申明)以实现相应,而组合式直接ref/reactive即可;
- 组合式中需要导入响应式函数和生命周期钩子等等,而选项式不需要;(除了编译器宏比如defineProps之外)
- 在这个项目中我主要使用组合式,往后就不管选项式了
<style scoped></style>
:用于定义组件的样式。开发者可以使用CSS或预处理器(如Sass、Less、Tailwind CSS,问perplexity)编写样式:<style>
标签内编写的 CSS 样式默认是全局作用域,会影响到其他路由页面。要使样式仅作用于当前组件,需要在<style>
标签上添加 scoped 属性:1
2
3
4
5
6
7
8
9
10
11<style lang="less" scoped>
/* 在这里添加样式 */
.logo-lg {
font-size: 20px;
text-align: center;
height: 50px;
line-height: 50px;
color: style
}
</style>
- 根组件App.vue:包含了整个应用程序的布局结构
- 页面渲染的入口点。所有其他组件都会嵌套在App.vue中
- 通常会与Vue Router的组件
<RouterView />
结合使用,处理页面之间的导航和路由,实现不同页面的切换和展示;如果使用了路由,但是App.vue中没有<RouterView />
,则路由无法正常工作;经典内容如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<RouterView />
</template>
<script setup>
</script>
<style scoped>
</style>
- Vue应用的入口文件main.js:初始化Vue实例并将其挂载到DOM中,导出了整个应用程序所需的全局状态和方法。一般来说,在这里初始化一些全局变量或函数,以便于后续操作,其基础操作如下:
- 导入router实例
import router from './router'
;(访问router/index.js文件) - 创建app实例
const app = createApp(App)
,这里的App是根组件App.vue; - 挂载router实例
app.use(router)
- 挂载app实例到指定ID的div元素上:
app.mount('#app')
- 这里的#app是index.html中的一个div元素的id,Vue会将app实例挂载到这个元素上,从而渲染整个应用
- 你也可以将app实例挂载到其他元素上,但要在index.html中添加相应的元素以及id属性
- 默认使用#app的原因
- 约定俗成:使用id=”app”是Vue文档和许多示例中的常见做法,主要是为了简化学习和开发过程。开发者可以快速识别出这是Vue应用的挂载点。
- 避免冲突:在大型应用中,使用特定的id可以帮助避免与其他JavaScript库或框架的冲突,确保Vue应用的DOM元素明确且独特。
- 导入router实例
- router文件夹:
- 功能:路由管理
- 导入组件,定义路由规则,控制页面间的跳转逻辑
- 定义 URL 路径与组件的对应关系
- 管理路由参数传递
- router/index.js负责初始化和配置Vue应用; 主要功能如下
- 定义路由routes
- 创造并导出router实例
- 注意:不能重复导入同一个组件,否则会导致组件重复注册,从而引发错误
- 典型结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
const routes = [
{
path: '/',
component: () => import('@/views/Home.vue')
},
{
path: '/about',
component: () => import('@/views/About.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
- 功能:路由管理
- View文件夹:
- 视图功能:负责页面渲染
- 存放页面级别的组件
- 对应具体的路由页面
- 组合和管理其他小组件
- 与components的区别
- views:负责页面级别的组件,对应具体路由逻辑的页面(也就是说路由怎么设置的,view下就要有相应的页面组件),组合和管理其他小组件(可嵌入components中或者view中子组件)
- components:负责通用的小组件,可以在多个页面中复用,不涉及具体的路由逻辑
- 举例 Main.vue
- 作用:主要负责页面的整体布局和结构,包含了整个页面的主要内容
- 典型结构
1
2
3
4
5
6
7
8<!-- views/Main.vue -->
<template>
<div class="main">
<Header />
<Aside />
<MainContent />
</div>
</template>
- 视图功能:负责页面渲染
1. services :存放业务逻辑层面的代码,包括多个组件共用的服务端接口调用函数等
2. utils:存放通用工具函数,不同的地方不重复书写相同的代码片段
执行顺序
- 当启动应用程序时,Vite 会按照如下顺序加载和处理这些文件:
1
2
3
4
5
6读取 package.json:获取项目依赖项和脚本信息。
解析 vue.config.js:配置Vite的行为,包括环境变量管理、插件注册等。
载入 src/App.vue:作为应用程序的根组件,负责渲染页面结构。
执行 src/main.js:初始化应用程序,全局状态和方法的创建。
递归查找并加载各个组件:从根组件开始,逐级向下寻找并注入子组件,形成完整的DOM树结构。
优化与缓存处理:利用Vite提供的内置优化机制,对JS/CSS进行压缩混淆,以及对第三方库进行tree-shaking以减少无效代码。