一、Vue 3 基础搭建与核心语法
1.创建 Vue 3 应用
在项目的入口文件 main.js
中,通过以下代码创建 Vue 3 应用实例:
javascript
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
app.mount('#app');
这几行代码的作用是引入 createApp
函数,创建一个应用实例,并将 App.vue
挂载到页面上指定的 #app
元素。
2.组合式 API
Vue 3 引入了 组合式 API(Composition API),提供了更灵活的逻辑复用方式。
- 组合逻辑 :Composition API 提供了一种新的方式来组织和复用逻辑代码。它允许开发者以函数的形式定义组件的逻辑,而不是像 Options API 那样分散在
data
、methods
、computed
等选项中。 - 生命周期钩子 :如
onMounted
,onUnmounted
等等,与 Options API 中的生命周期钩子相对应,但更灵活,可以自由组合。 setup
:组件的入口函数,用于定义响应式数据、计算属性、方法等。
javascript
<script setup>
import { ref, onMounted } from 'vue';
const message = ref('Hello World');
onMounted(() => {
console.log('Component is mounted!');
});
</script>
<template>
<p>{{ message }}</p>
</template>
javascript
import { ref, onMounted } from 'vue';
export default {
setup() {
const count = ref(0);
onMounted(() => {
console.log('Component mounted');
});
return {
count,
};
},
};
3.<script setup>
语法
<script setup>
是 Vue 3 提供的重要语法糖,极大地简化了组件的代码结构。在该语法下,模块顶层的代码会自动编译到 setup()
函数中,无需显式定义 setup
函数。
二、响应式原理与数据处理
Vue 3 使用 Proxy 实现响应式系统,Vue 3 使用 Proxy 替代了 Vue 的 Object.defineProperty
,实现了更强大的响应式系统。
1.ref
和 reactive
ref
和reactive
:ref
用于创建简单的响应式引用,而reactive
则用来处理对象。对于数组和对象,建议使用reactive
。.value
访问器 :当使用ref
创建的响应式引用时,需要通过.value
来读取或设置其内部值。
javascript
<script setup>
import { ref, reactive } from 'vue';
// 使用 ref 创建基本类型响应式变量
const count = ref(0);
// 使用 reactive 创建复杂类型的响应式对象
const state = reactive({
name: 'Vue',
version: 3,
});
function increment() {
count.value++;
}
</script>
<template>
<div>
<p>Count is {{ count }}</p>
<button @click="increment">Increment</button>
<p>Name is {{ state.name }}, Version is {{ state.version }}</p>
</div>
</template>
2.toRefs
toRefs
函数用于将一个响应式对象转换为普通对象,其中每个属性都转换为一个 ref
,这样在解构响应式对象时能够保持其响应性。
javascript
<template>
<div>
<p>姓名: {{ name }}</p>
<p>年龄: {{ age }}</p>
</div>
</template>
<script setup>
import { reactive, toRefs } from 'vue';
const person = reactive({
name: '王五',
age: 30
});
const { name, age } = toRefs(person);
</script>
三、组件化开发
1.组件定义与使用
Vue 3 使用 .vue
文件来定义组件,包含 <template>
、<script>
和 <style>
三个部分。
javascript
<template>
<div>
<p>{{ message }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const message = ref('Hello, Vue 3!');
const increment = () => {
message.value += '!';
};
return {
message,
increment,
};
},
};
</script>
<style scoped>
p {
color: blue;
}
</style>
使用 <script setup>
定义组件十分便捷,只需在 <script setup>
标签内编写逻辑代码,在 <template>
标签内编写模板内容。
javascript
// 子组件 Child.vue
<template>
<div>
<p>这是一个子组件</p>
</div>
</template>
<script setup>
// 这里可以定义子组件的逻辑
</script>
在父组件中使用子组件时,先导入子组件,然后在模板中直接使用子组件标签即可。
javascript
// 父组件 Parent.vue
<template>
<div>
<Child />
</div>
</template>
<script setup>
import Child from './Child.vue';
</script>
2.组件通信
-
Props:父组件向子组件传递数据。
-
事件:子组件向父组件传递数据。
父传子
通过 props
实现父组件向子组件传递数据。
javascript
// 父组件 Parent.vue
<template>
<div>
<ChildComponent :message="parentMessage" />
</div>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
const parentMessage = '父组件传递的信息';
</script>
javascript
// 子组件 ChildComponent.vue
<template>
<div>
<p>{{ message }}</p>
</div>
</template>
<script setup>
const props = defineProps(['message']);
</script>
子传父
子组件通过 emit
触发事件向父组件传递数据。
javascript
// 子组件 Child.vue
<template>
<div>
<button @click="sendDataToParent">传递数据给父组件</button>
</div>
</template>
<script setup>
import { defineEmits } from 'vue';
const emits = defineEmits(['dataFromChild']);
const sendDataToParent = () => {
emits('dataFromChild', '子组件的数据');
};
</script>
javascript
// 父组件 Parent.vue
<template>
<div>
<Child @dataFromChild="handleDataFromChild" />
</div>
</template>
<script setup>
import Child from './Child.vue';
const handleDataFromChild = (data) => {
console.log('接收到子组件的数据:', data);
};
</script>
兄弟组件通信
兄弟组件通信通常借助事件总线或状态管理库(如 Pinia)来实现。事件总线可以通过创建一个空的 Vue 实例来作为事件中心,兄弟组件通过这个实例来触发和监听事件。使用 Pinia 时,将共享的数据存储在 Pinia 的 store 中,兄弟组件都可以访问和修改这个 store 中的数据。
3.插槽
匿名插槽
父组件可以在子组件标签内插入内容,子组件通过 <slot>
标签渲染这些内容。
javascript
// 父组件 Parent.vue
<template>
<div>
<ChildComponent>
<p>这是插入到子组件的匿名插槽内容</p>
</ChildComponent>
</div>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
</script>
javascript
// 子组件 ChildComponent.vue
<template>
<div>
<slot></slot>
</div>
</template>
<script setup>
</script>
具名插槽
父组件可以通过 template
标签和 #name
语法指定插槽名称,子组件通过 <slot name="xxx">
渲染对应的内容。
javascript
// 父组件 Parent.vue
<template>
<div>
<ChildComponent>
<template #header>
<h1>这是头部插槽内容</h1>
</template>
<template #content>
<p>这是内容插槽内容</p>
</template>
</ChildComponent>
</div>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
</script>
javascript
// 子组件 ChildComponent.vue
<template>
<div>
<slot name="header"></slot>
<slot name="content"></slot>
</div>
</template>
<script setup>
</script>
四、生命周期管理
在 <script setup>
中,可以方便地使用各种生命周期钩子函数,这些钩子函数在组件的不同阶段被调用。
javascript
<template>
<div>
<p>组件内容</p>
</div>
</template>
<script setup>
import { onMounted, onUpdated, onUnmounted } from 'vue';
onMounted(() => {
console.log('组件已挂载');
});
onUpdated(() => {
console.log('组件已更新');
});
onUnmounted(() => {
console.log('组件已卸载');
});
</script>
onMounted
在组件挂载完成后调用,可用于初始化数据、获取 DOM 元素等操作;onUpdated
在组件更新后调用,可用于在数据更新后执行一些额外的逻辑;onUnmounted
在组件卸载时调用,可用于清理定时器、解绑事件等操作。
五、计算属性与监听器
1.计算属性
计算属性是基于响应式数据的缓存值,只有当它所依赖的响应式数据发生变化时才会重新计算。
javascript
<template>
<div>
<p>原始数字: {{ number }}</p>
<p>加倍后的数字: {{ doubleNumber }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const number = ref(10);
const doubleNumber = computed(() => number.value * 2);
</script>
在这个例子中,doubleNumber
是一个计算属性,依赖于 number
的值。当 number
变化时,doubleNumber
会自动重新计算。
2.监听器
监听器用于监听响应式数据的变化,并在数据变化时执行相应的操作。
javascript
<template>
<div>
<input v-model="inputValue" />
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
const inputValue = ref('');
watch(inputValue, (newValue, oldValue) => {
console.log('新值:', newValue, '旧值:', oldValue);
});
</script>
上述代码中,通过 watch
监听 inputValue
的变化,当 inputValue
改变时,会在控制台打印出新值和旧值。
六、指令系统
1.内置指令
v-model
v-model
用于表单元素的双向数据绑定,使得表单元素的值与响应式数据保持同步。
javascript
<template>
<div>
<input v-model="inputText" />
<p>输入的内容: {{ inputText }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
const inputText = ref('');
</script>
v-if、v-else、v-else-if
这些指令用于条件渲染,根据表达式的真假来决定是否渲染相应的内容。
javascript
<template>
<div>
<p v-if="isShow">显示的内容</p>
<p v-else>不显示的内容</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
const isShow = ref(true);
</script>
v-for
v-for
用于列表渲染,通过遍历数组或对象来渲染多个元素。
javascript
<template>
<div>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue';
const list = ref(['苹果', '香蕉', '橙子']);
</script>
v-bind
v-bind
可以缩写为 :
,用于动态绑定 HTML 属性,使属性值根据响应式数据的变化而变化。
javascript
<template>
<div>
<img :src="imageUrl" :alt="imageAlt" />
</div>
</template>
<script setup>
import { ref } from 'vue';
const imageUrl = ref('path/to/image.jpg');
const imageAlt = ref('描述图片');
</script>
v-on
v-on
可以缩写为 @
,用于绑定事件监听器,当事件触发时执行相应的函数。
javascript
<template>
<div>
<button @click="handleClick">点击我</button>
</div>
</template>
<script setup>
const handleClick = () => {
console.log('按钮被点击了');
};
</script>
2.自定义指令
自定义指令可以扩展 Vue 的功能,通过 directive
函数来定义。
javascript
<template>
<div>
<input v-focus />
</div>
</template>
<script setup>
import { directive } from 'vue';
const vFocus = directive('focus', {
mounted(el) {
el.focus();
}
});
</script>
上述代码定义了一个自定义指令 v-focus
,当组件挂载时,会自动将焦点设置在使用该指令的输入框上。
七、状态管理(Pinia)
Vue 3 推荐使用 Pinia 作为状态管理库。
1.安装与引入
首先通过 npm install pinia
安装 Pinia,然后在 main.js
中引入并使用:
javascript
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
const pinia = createPinia();
const app = createApp(App);
app.use(pinia);
app.mount('#app');
2.创建 Store
使用 defineStore
函数定义一个 store,用于存储和管理应用的状态。
javascript
// store/counter.js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++;
}
}
});
3.在组件中使用 Store
在组件中通过 useCounterStore
函数来访问和使用 store 中的状态和方法。
javascript
<template>
<div>
<p>计数器: {{ counterStore.count }}</p>
<button @click="counterStore.increment">增加</button>
</div>
</template>
<script setup>
import { useCounterStore } from './store/counter.js';
const counterStore = useCounterStore();
</script>
八、路由管理(Vue Router)
1.安装与配置
通过 npm install vue-router
安装 Vue Router,然后在 router/index.js
中配置路由:
javascript
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
// 配置使用history模式
history: createWebHistory(import.meta.env.BASE_URL),
// 定义路由规则
routes: [
{
path: '/', // 路由名称
name: 'home',
component: HomeView // 同步组件
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue') // 异步组件
}
]
})
export default router
2.动态路由和嵌套路由
-
动态路由 :通过
:id
定义动态路径参数。 -
嵌套路由 :通过
children
定义嵌套路由。
javascript
const routes = [
{
path: '/user/:id',
component: User,
children: [
{ path: 'profile', component: Profile },
{ path: 'posts', component: Posts },
],
},
];
3.在 main.js
中引入路由
在 main.js
中引入配置好的路由:
javascript
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
3.在组件中使用路由
在组件中可以使用 router-link
来创建导航链接,使用 router-view
来显示当前路由对应的组件。
javascript
<script setup>
// RouterLink是一个组件
// RouterView也是一个组件
import { RouterLink, RouterView } from 'vue-router'
</script>
<template>
<!-- 配置路由出口 -->
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</nav>
<main>
<router-view></router-view>
</main>
</template>
<style scoped>
nav {
display: flex;
width: 200px;
}
nav a {
flex: 1;
}
main {
width: 400px;
height: 200px;
border: 1px solid #ccc;
}
</style>
九、性能优化
1 懒加载
-
组件懒加载 :使用
defineAsyncComponent
或import()
动态加载组件。 -
路由懒加载 :使用
import()
动态加载路由组件。
javascript
const Home = defineAsyncComponent(() => import('./views/Home.vue'));
2 代码拆分
- 使用 Webpack 或 Vite 的代码拆分功能,将代码分割成多个 chunk。
3 缓存组件
- 使用
<keep-alive>
缓存组件状态,避免重复渲染。
javascript
<keep-alive>
<router-view />
</keep-alive>
4.Teleport
- 传送内容到DOM的不同位置:允许你把组件的内容"传送"到应用程序中的任意位置,而不受组件层级结构的限制。这对于模态框、提示信息等场景特别有用。
javascript
<teleport to="body">
<!-- 这里的内容会被插入到 body 元素中 -->
<div id="modal">...</div>
</teleport>
5.Transition 组件增强
TransitionGroup
和 Transition
:提供了更强大的过渡效果控制,包括列表动画等功能。
6. Suspense
- 异步组件加载:允许你在等待异步依赖(如从服务器获取数据)时展示占位符内容,直到所需资源准备好为止。
javascript
<suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
Loading...
</template>
</suspense>
十、最佳实践
1 组件设计
-
单一职责:每个组件只负责一个功能。
-
Props 验证 :使用
props
的type
和validator
进行数据验证。
javascript
export default {
props: {
message: {
type: String,
required: true,
validator: (value) => value.length > 0,
},
},
};
2 代码风格
-
使用组合式 API 组织逻辑。
-
使用
ref
和reactive
定义响应式数据。 -
使用
computed
和watch
处理复杂逻辑。
3 错误处理
-
使用
try...catch
捕获异步错误。 -
使用全局错误处理函数捕获未处理的异常。
javascript
app.config.errorHandler = (err, vm, info) => {
console.error('Global error:', err);
};
十一、 工具链
1 Vite
Vite 是 Vue 3 的推荐构建工具,支持快速开发和热更新。
2 Vue DevTools
Vue DevTools 是浏览器扩展,用于调试 Vue 应用。
十二、测试
1 单元测试
使用 Jest 或 Vitest 进行单元测试。
javascript
import { mount } from '@vue/test-utils';
import MyComponent from './MyComponent.vue';
test('renders correctly', () => {
const wrapper = mount(MyComponent);
expect(wrapper.text()).toContain('Hello, Vue 3!');
});
2 端到端测试
使用 Cypress 进行端到端测试。
javascript
describe('MyComponent', () => {
it('renders correctly', () => {
cy.visit('/');
cy.contains('Hello, Vue 3!');
});
});
十三、TypeScript 支持
- 内置对 TypeScript 的友好支持:包括类型推断、类型检查以及更好的开发体验。Composition API 特别适合 TypeScript,因为它提供了更明确的类型定义。
javascript
<template>
<p>{{ message }}</p>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const message = ref<string>('Hello, Vue 3 with TypeScript!');
</script>