Vue3学习

一、Vue 3 概述

1. Vue.js 简介

Vue.js 是一个渐进式的 JavaScript 框架,专注于构建用户界面。Vue 可以与其他库或现有项目进行集成,也可以用于搭建复杂的单页应用(SPA)。

2. Vue.js 的特点和优势

  • 渐进式框架:可以逐步采用 Vue,灵活性高。
  • 响应式数据绑定:通过数据和视图的双向绑定,简化了数据管理。
  • 组件化:支持组件化开发,促进代码复用和可维护性。
  • 虚拟 DOM:通过虚拟 DOM 提高渲染性能,降低直接操作 DOM 的开销。
  • 生态系统完整:提供路由管理、状态管理和构建工具等完整的生态支持。

3. Vue 3 的新特性

3.1 性能优化
  • 虚拟 DOM 的优化:Vue 3 通过 Proxy API 替代了 Vue 2 中的 Object.defineProperty,使得数据的响应式跟踪更加高效。
  • 更小的包体积:Vue 3 的核心库体积更小,提升了加载速度。
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 创建一个简单的 Vue 3 应用
const { createApp, ref } = Vue;

const App = {
setup() {
const count = ref(0);

const increment = () => {
count.value++;
};

return { count, increment };
},
template: `<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>`
};

createApp(App).mount('#app');
3.2 Composition API

Composition API 是 Vue 3 的一个新特性,允许开发者在 setup() 函数中组织逻辑,提升代码的可读性和复用性。

示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const { createApp, ref, computed } = Vue;

const App = {
setup() {
const num1 = ref(1);
const num2 = ref(2);

const sum = computed(() => num1.value + num2.value);

return { num1, num2, sum };
},
template: `<div>
<input v-model="num1" type="number" />
<input v-model="num2" type="number" />
<p>Sum: {{ sum }}</p>
</div>`
};

createApp(App).mount('#app');
3.3 Fragments

在 Vue 3 中,组件可以返回多个根节点,而不需要被包裹在一个根节点中。

示例代码:
1
2
3
4
5
6
7
8
9
10
const { createApp } = Vue;

const App = {
template: `
<h1>Hello</h1>
<h2>World</h2>
`
};

createApp(App).mount('#app');
3.4 Teleport

Teleport 允许组件被渲染到 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
const { createApp, ref } = Vue;

const App = {
setup() {
const showModal = ref(false);

const toggleModal = () => {
showModal.value = !showModal.value;
};

return { showModal, toggleModal };
},
template: `
<div>
<button @click="toggleModal">Toggle Modal</button>
<teleport to="body">
<div v-if="showModal" style="border: 1px solid black; padding: 20px; background: white; position: fixed; top: 20%; left: 50%; transform: translate(-50%, -50%);">
<p>This is a modal!</p>
<button @click="toggleModal">Close</button>
</div>
</teleport>
</div>
`
};

createApp(App).mount('#app');
3.5 Suspense

Suspense 组件用于处理异步组件的加载状态,可以在组件加载时显示一个占位符(fallback)。

示例代码:
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
const { createApp, defineAsyncComponent } = Vue;

const AsyncComponent = defineAsyncComponent(() => {
return new Promise((resolve) => {
setTimeout(() => resolve({ template: '<h2>Loaded!</h2>' }), 2000);
});
});

const App = {
template: `
<div>
<h1>Hello</h1>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<h2>Loading...</h2>
</template>
</Suspense>
</div>
`
};

createApp(App).mount('#app');

二、环境搭建

1. 开发环境准备

1.1 安装 Node.js 和 npm

Node.js 是一个 JavaScript 运行环境,用于在服务器端执行 JavaScript 代码。npm 是 Node.js 的包管理工具,用于安装和管理 JavaScript 库和工具。

  • 安装步骤
    1. 下载 Node.js:访问 Node.js 官方网站,根据你的操作系统(Windows、macOS 或 Linux)下载合适的安装包。
    2. 安装 Node.js:运行下载的安装包,并按照提示完成安装。安装过程中请确保选中“Add to PATH”选项,这样可以在命令行中直接使用 nodenpm 命令。
    3. 验证安装:安装完成后,可以打开命令行(终端)并输入以下命令来验证安装是否成功:
      • node -v:查看 Node.js 的版本。
      • npm -v:查看 npm 的版本。
1.2 创建 Vue 3 项目

可以使用 Vue CLI 或 Vite 创建 Vue 3 项目,这两者都是创建 Vue 项目的常用工具。

1.2.1 使用 Vue CLI

Vue CLI 是 Vue.js 官方提供的一个脚手架工具,适合中大型项目。

  • 安装 Vue CLI

    1. 在命令行中执行以下命令安装 Vue CLI:
      1
      npm install -g @vue/cli
    2. 验证安装:
      1
      vue --version
  • 创建 Vue 3 项目

    1. 使用 CLI 创建新项目:
      1
      vue create my-vue-app
    2. 在创建过程中,会提示选择配置,可以选择默认配置或手动选择特性(如 TypeScript、Vue Router、Vuex 等)。
    3. 进入项目目录:
      1
      cd my-vue-app
    4. 启动开发服务器:
      1
      npm run serve
1.2.2 使用 Vite

Vite 是一个新兴的前端构建工具,提供更快速的构建和热重载体验,适合小型和中型项目。

  • 创建 Vue 3 项目
    1. 在命令行中执行以下命令创建新项目:
      1
      npm init vite@latest my-vue-app -- --template vue
    2. 进入项目目录:
      1
      cd my-vue-app
    3. 安装依赖:
      1
      npm install
    4. 启动开发服务器:
      1
      npm run dev

2. 项目结构

无论是通过 Vue CLI 还是 Vite 创建的 Vue 3 项目,项目的基本结构都比较相似。以下是项目目录结构及各个文件的说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
my-vue-app/
├── node_modules/ # 项目依赖的第三方库
├── public/ # 静态资源目录,直接拷贝到最终构建中
│ └── favicon.ico # 应用的图标
├── src/ # 源代码目录
│ ├── assets/ # 静态资源(图片、字体、样式等)
│ ├── components/ # Vue 组件
│ ├── App.vue # 根组件
│ ├── main.js # 入口文件
│ └── router/ # 路由配置(如果使用了 Vue Router)
│ └── index.js # 路由定义
├── .gitignore # Git 忽略文件列表
├── package.json # 项目信息和依赖管理
├── README.md # 项目说明文件
└── vite.config.js # Vite 配置文件(如果使用 Vite)
2.1 目录结构和文件说明
  • **node_modules/**:存放所有项目依赖的第三方库,npm 安装的所有包都会放在这里。
  • **public/**:用于存放静态文件,如图片、favicon、以及一些不需要 Webpack 处理的文件。这些文件会被直接拷贝到构建输出中。
  • **src/**:项目的源代码目录,所有的业务逻辑和组件都放在这里。
    • **assets/**:存放静态资源,如图片、字体、样式等。
    • **components/**:存放自定义的 Vue 组件,建议按功能或模块分组。
    • App.vue:根组件,整个应用的入口组件,通常包含应用的结构和样式。
    • main.js:入口文件,负责创建 Vue 实例并将根组件挂载到 DOM。
    • **router/**:如果使用了 Vue Router,这里存放路由配置文件。
      • index.js:路由的定义,包含路由路径、组件与其他路由配置。
  • .gitignore:Git 忽略文件,列出不需要添加到版本控制中的文件和目录。
  • package.json:包含项目的依赖、脚本、版本、描述等信息,是 Node.js 项目的核心配置文件。
  • README.md:项目的说明文档,通常包括项目介绍、安装和使用说明、贡献指导等信息。
  • vite.config.js:如果使用 Vite,这里存放 Vite 的配置文件,可以进行构建和开发的配置。

三、基础知识

1. Vue 实例

1.1 创建 Vue 实例

在 Vue 中,所有的应用都是通过创建 Vue 实例来开始的。Vue 实例是 Vue 应用的核心,它将数据、模板和视图绑定在一起。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 导入 Vue
import { createApp } from 'vue';

// 创建 Vue 应用
const app = createApp({
data() {
return {
message: 'Hello Vue!'
};
}
});

// 挂载 Vue 应用到 DOM
app.mount('#app');

在这个示例中,我们创建了一个 Vue 应用,定义了一个 data 数据属性 message,并将应用挂载到 id 为 app 的 DOM 元素上。

1.2 生命周期钩子

每个 Vue 实例在创建、更新和销毁的不同阶段都有一系列的生命周期钩子。生命周期钩子允许开发者在特定的时间点执行代码。

常用生命周期钩子

  • created:实例创建后立即调用。
  • mounted:挂载后调用,适合进行与 DOM 相关的操作。
  • updated:数据变化导致视图更新后调用。
  • beforeUnmount:组件销毁前调用。
  • unmounted:组件销毁后调用。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const app = createApp({
data() {
return {
message: 'Hello Vue!'
};
},
created() {
console.log('实例已创建');
},
mounted() {
console.log('组件已挂载');
},
unmounted() {
console.log('组件已销毁');
}
});

app.mount('#app');

在这个示例中,我们在实例的生命周期钩子中添加了简单的日志输出,便于观察生命周期的变化。

2. 模板语法

2.1 Mustache 语法

Mustache 语法是一种简单的文本插值方式,使用双大括号 {{ }} 来绑定数据。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="app">
<h1>{{ message }}</h1>
</div>

<script>
const app = createApp({
data() {
return {
message: 'Hello Vue!'
};
}
});

app.mount('#app');
</script>

在这个示例中,Vue 会将 {{ message }} 替换为 data 中的 message 的值。

2.2 指令

指令是 Vue 模板中的特殊特性,用于对 DOM 元素进行操作。

  • **v-bind**:动态绑定 HTML 属性。
  • **v-model**:实现双向数据绑定。
  • **v-if**:条件渲染。
  • **v-for**:列表渲染。
  • **v-show**:控制元素的显示与隐藏。
  • **v-on**:绑定事件监听器。

代码示例

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
<div id="app">
<h1>{{ message }}</h1>
<button @click="toggle">Toggle Message</button>
<p v-if="isVisible">This is a toggle message!</p>
<ul>
<li v-for="item in items" :key="item.id">{{ item.text }}</li>
</ul>
<input v-model="inputValue" placeholder="Type something" />
<p>Typed: {{ inputValue }}</p>
</div>

<script>
const app = createApp({
data() {
return {
message: 'Hello Vue!',
isVisible: true,
inputValue: '',
items: [
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' }
]
};
},
methods: {
toggle() {
this.isVisible = !this.isVisible;
}
}
});

app.mount('#app');
</script>

在这个示例中,我们演示了多种指令的用法,包括动态绑定、条件渲染、列表渲染和双向数据绑定。

3. 计算属性和侦听器

3.1 计算属性的使用

计算属性是基于响应式数据的动态计算值,适合用于生成基于数据的值。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="app">
<input v-model="firstName" placeholder="First Name" />
<input v-model="lastName" placeholder="Last Name" />
<p>Full Name: {{ fullName }}</p>
</div>

<script>
const app = createApp({
data() {
return {
firstName: '',
lastName: ''
};
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
});

app.mount('#app');
</script>

在这个示例中,fullName 是一个计算属性,它根据 firstNamelastName 的值动态生成完整的名字。

3.2 侦听器的使用和区别

侦听器用于观察响应式数据的变化,并在数据变化时执行操作。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="app">
<input v-model="count" placeholder="Enter a number" />
<p>Count: {{ count }}</p>
</div>

<script>
const app = createApp({
data() {
return {
count: 0
};
},
watch: {
count(newValue, oldValue) {
console.log(`Count changed from ${oldValue} to ${newValue}`);
if (newValue > 10) {
alert('Count is greater than 10!');
}
}
}
});

app.mount('#app');
</script>

在这个示例中,当 count 的值变化时,侦听器会输出变化的值,并在 count 超过 10 时弹出警告。

四、组件

1. 组件基本概念

1.1 组件的定义和注册

组件是 Vue 中的基本构建块。组件可以包含自己的模板、逻辑和样式,使得代码更加模块化和可重用。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- App.vue -->
<template>
<div id="app">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue';

export default {
components: {
HelloWorld
}
}
</script>

<style>
/* 样式 */
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- HelloWorld.vue -->
<template>
<div>
<h1>{{ msg }}</h1>
</div>
</template>

<script>
export default {
props: {
msg: String
}
}
</script>

<style scoped>
h1 {
color: blue;
}
</style>

在这个示例中,我们定义了一个 HelloWorld 组件,并在 App.vue 中注册和使用它。组件通过 props 接收外部数据。

1.2 组件的模板、script 和 style

每个组件由三个部分组成:模板(template)、脚本(script)和样式(style)。模板定义了组件的结构,脚本包含逻辑和状态,样式定义了组件的外观。

  • 模板:使用 Vue 的模板语法定义组件的 HTML 结构。
  • 脚本:在 script 标签中定义组件的逻辑、数据和方法。
  • 样式:在 style 标签中定义组件的样式,可以使用 scoped 属性限制样式的作用域。

2. 组件间通信

2.1 Props 的使用

props 是父组件向子组件传递数据的方式。子组件通过 props 接收父组件传递的数据。

代码示例

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
<!-- Parent.vue -->
<template>
<div>
<Child :name="parentName" />
</div>
</template>

<script>
import Child from './Child.vue';

export default {
components: {
Child
},
data() {
return {
parentName: 'Parent Component'
};
}
}
</script>

<!-- Child.vue -->
<template>
<div>
<p>Received from parent: {{ name }}</p>
</div>
</template>

<script>
export default {
props: {
name: String
}
}
</script>

在这个示例中,Parent.vue 通过 propsChild.vue 传递一个名为 name 的属性,子组件可以直接使用这个属性。

2.2 Emit 事件

子组件通过 $emit 方法向父组件发送事件,从而实现子组件向父组件的通信。

代码示例

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
<!-- Parent.vue -->
<template>
<div>
<Child @childEvent="handleChildEvent" />
</div>
</template>

<script>
import Child from './Child.vue';

export default {
components: {
Child
},
methods: {
handleChildEvent(data) {
console.log('Event received from child:', data);
}
}
}
</script>

<!-- Child.vue -->
<template>
<button @click="sendEvent">Send Event to Parent</button>
</template>

<script>
export default {
methods: {
sendEvent() {
this.$emit('childEvent', 'Hello from Child!');
}
}
}
</script>

在这个示例中,Child.vue 通过 $emit 发送一个名为 childEvent 的事件,父组件 Parent.vue 监听这个事件,并执行 handleChildEvent 方法。

2.3 Provide/Inject

provide/inject 是一种用于祖孙组件之间通信的方式,可以在不直接通过 props 和事件传递的情况下共享数据。

代码示例

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
<!-- Grandparent.vue -->
<template>
<div>
<Parent />
</div>
</template>

<script>
import Parent from './Parent.vue';

export default {
components: {
Parent
},
provide() {
return {
sharedData: 'Data from Grandparent'
};
}
}
</script>

<!-- Parent.vue -->
<template>
<Child />
</template>

<script>
import Child from './Child.vue';

export default {
components: {
Child
}
}
</script>

<!-- Child.vue -->
<template>
<p>{{ sharedData }}</p>
</template>

<script>
export default {
inject: ['sharedData']
}
</script>

在这个示例中,Grandparent.vue 提供了 sharedData,而 Child.vue 通过 inject 接收这个数据。这样,Child 可以直接访问 Grandparent 提供的数据。

3. 插槽

3.1 默认插槽

默认插槽允许父组件在子组件中插入内容。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Parent.vue -->
<template>
<Child>
<p>This is content from the parent.</p>
</Child>
</template>

<!-- Child.vue -->
<template>
<div>
<slot></slot>
</div>
</template>

在这个示例中,Parent.vue 的内容通过默认插槽被插入到 Child.vue 中。

3.2 具名插槽

具名插槽允许父组件为插槽指定名称,以便在子组件中插入特定的内容。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- Parent.vue -->
<template>
<Child>
<template v-slot:header>
<h1>This is a header</h1>
</template>
<template v-slot:footer>
<footer>This is a footer</footer>
</template>
</Child>
</template>

<!-- Child.vue -->
<template>
<div>
<slot name="header"></slot>
<p>This is the main content.</p>
<slot name="footer"></slot>
</div>
</template>

在这个示例中,Parent.vueChild.vue 中的两个具名插槽提供了内容:一个是 header,另一个是 footer

3.3 作用域插槽

作用域插槽允许父组件访问子组件中的数据。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- Parent.vue -->
<template>
<Child>
<template v-slot:default="{ msg }">
<p>{{ msg }}</p>
</template>
</Child>
</template>

<!-- Child.vue -->
<template>
<slot :msg="message"></slot>
</template>

<script>
export default {
data() {
return {
message: 'This is a message from the child.'
};
}
}
</script>

在这个示例中,Child.vue 提供了一个作用域插槽,父组件通过 v-slot 访问到子组件 message 数据。

五、Composition API

1. 基本概念

1.1 什么是 Composition API

Composition API 是 Vue 3 引入的新特性,旨在提供一种更灵活和可组合的方式来组织和复用逻辑。与传统的选项 API(Options API)不同,Composition API 通过函数的方式来处理状态和逻辑,使得组件的逻辑更加清晰和易于管理。

1.2 使用 setup() 函数

setup() 函数是 Composition API 的核心,它在组件创建之前被调用,并且是组件实例化的第一个生命周期钩子。所有的响应式状态、计算属性和方法都可以在 setup() 中定义。

代码示例

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
<template>
<div>
<h1>{{ message }}</h1>
<button @click="increment">Increment</button>
<p>Count: {{ count }}</p>
</div>
</template>

<script>
import { ref } from 'vue';

export default {
setup() {
// 定义响应式状态
const message = ref('Hello Composition API');
const count = ref(0);

// 定义方法
const increment = () => {
count.value++;
};

// 返回要在模板中使用的状态和方法
return {
message,
count,
increment
};
}
}
</script>

在这个示例中,我们在 setup() 函数中定义了一个响应式的 messagecount,并创建了一个 increment 方法来增加计数。

2. 响应式 API

2.1 refreactive 的使用
  • **ref**:用于创建一个基本数据类型的响应式引用。
  • **reactive**:用于创建一个对象的响应式状态,适合用于复杂数据结构。

代码示例

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
<template>
<div>
<h2>User Info</h2>
<p>Name: {{ user.name }}</p>
<p>Age: {{ user.age }}</p>
<button @click="updateAge">Increase Age</button>
</div>
</template>

<script>
import { reactive } from 'vue';

export default {
setup() {
// 使用 reactive 创建响应式对象
const user = reactive({
name: 'John Doe',
age: 25
});

const updateAge = () => {
user.age++;
};

return {
user,
updateAge
};
}
}
</script>

在这个示例中,我们使用 reactive 创建了一个 user 对象,包含 nameage 属性,并提供了一个方法来增加年龄。

2.2 computedwatch 的使用
  • **computed**:用于创建计算属性,可以基于响应式状态自动计算值。
  • **watch**:用于观察响应式数据的变化,并在变化时执行某些操作。

代码示例

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
<template>
<div>
<input v-model="firstName" placeholder="First Name" />
<input v-model="lastName" placeholder="Last Name" />
<p>Full Name: {{ fullName }}</p>
<button @click="resetNames">Reset Names</button>
</div>
</template>

<script>
import { ref, computed, watch } from 'vue';

export default {
setup() {
const firstName = ref('');
const lastName = ref('');

// 创建计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});

// 观察数据变化
watch(fullName, (newValue) => {
console.log('Full name changed:', newValue);
});

const resetNames = () => {
firstName.value = '';
lastName.value = '';
};

return {
firstName,
lastName,
fullName,
resetNames
};
}
}
</script>

在这个示例中,我们使用 computed 创建了一个 fullName 计算属性,并使用 watch 观察它的变化。当 fullName 变化时,控制台会打印新的全名。

3. 逻辑复用

3.1 自定义组合函数 (Composition Functions)

自定义组合函数是一种逻辑复用的方法,可以将组合的逻辑提取到单独的函数中,从而在多个组件中使用。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- useCounter.js -->
import { ref } from 'vue';

export function useCounter() {
const count = ref(0);
const increment = () => {
count.value++;
};
const reset = () => {
count.value = 0;
};

return {
count,
increment,
reset
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- Counter.vue -->
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
<button @click="reset">Reset</button>
</div>
</template>

<script>
import { useCounter } from './useCounter.js';

export default {
setup() {
const { count, increment, reset } = useCounter();
return {
count,
increment,
reset
};
}
}
</script>

在这个示例中,我们定义了一个自定义组合函数 useCounter,它返回一个响应式计数器的状态和操作方法。然后我们在组件 Counter.vue 中使用这个组合函数,以实现计数功能。

六、路由管理

1. Vue Router 基础

1.1 安装和配置 Vue Router

在 Vue 3 中使用 Vue Router 进行路由管理,首先需要安装 Vue Router:

1
npm install vue-router@4

然后,在 Vue 应用中配置 Vue Router。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';

const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
];

const router = createRouter({
history: createWebHistory(),
routes
});

export default router;

接下来,在主应用文件中引入并使用这个路由。

1
2
3
4
5
6
7
8
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

const app = createApp(App);
app.use(router);
app.mount('#app');

在这个示例中,我们创建了两个基本路由:一个指向主页 Home,另一个指向关于页面 About

1.2 路由定义和嵌套路由

路由可以通过定义 children 属性来实现嵌套。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// router/index.js
const routes = [
{
path: '/',
name: 'Home',
component: Home,
children: [
{
path: 'profile',
name: 'Profile',
component: () => import('../views/Profile.vue') // 异步加载
}
]
}
];

在这个示例中,ProfileHome 的嵌套路由,当用户访问 /profile 时,会渲染 Profile.vue 组件。

2. 路由导航

2.1 编程式导航

可以使用编程式导航来在路由之间进行跳转。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>
<h1>Home Page</h1>
<button @click="goToAbout">Go to About</button>
</div>
</template>

<script>
import { useRouter } from 'vue-router';

export default {
setup() {
const router = useRouter();

const goToAbout = () => {
router.push('/about');
};

return {
goToAbout
};
}
}
</script>

在这个示例中,用户点击按钮时会通过 router.push 方法导航到关于页面。

2.2 路由守卫

路由守卫用于在路由变化时执行某些逻辑,例如身份验证。

代码示例

1
2
3
4
5
6
7
8
9
// router/index.js
router.beforeEach((to, from, next) => {
const isAuthenticated = false; // 假设的身份验证状态
if (to.name !== 'Home' && !isAuthenticated) {
next({ name: 'Home' }); // 未登录重定向到首页
} else {
next(); // 继续导航
}
});

在这个示例中,如果用户未登录(即 isAuthenticatedfalse),且试图访问不是首页的路由,则会被重定向到首页。

3. 路由参数

3.1 动态路由和查询参数

动态路由允许在路径中定义参数,例如用户 ID。查询参数则可以通过 URL 传递额外的数据。

代码示例

1
2
3
4
5
6
7
8
// router/index.js
const routes = [
{
path: '/user/:id',
name: 'User',
component: () => import('../views/User.vue')
}
];

User.vue 组件中,可以通过 $route.params 访问动态路由参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>
<h1>User ID: {{ userId }}</h1>
</div>
</template>

<script>
import { onMounted, ref } from 'vue';

export default {
setup(props, { root }) {
const userId = ref('');

onMounted(() => {
userId.value = root.$route.params.id;
});

return {
userId
};
}
}
</script>

用户可以访问 /user/123User.vue 会显示 User ID: 123

3.2 查询参数

查询参数在 URL 中以 ? 开头,后面跟着键值对。例如:/user?id=123

代码示例

1
2
3
4
5
6
7
8
// router/index.js
const routes = [
{
path: '/user',
name: 'User',
component: () => import('../views/User.vue')
}
];

User.vue 中获取查询参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>
<h1>User ID: {{ userId }}</h1>
</div>
</template>

<script>
import { onMounted, ref } from 'vue';

export default {
setup(props, { root }) {
const userId = ref('');

onMounted(() => {
userId.value = root.$route.query.id; // 通过 query 获取参数
});

return {
userId
};
}
}
</script>

在这个示例中,访问 /user?id=123User.vue 将显示 User ID: 123

七、状态管理

1. Vuex 简介

1.1 Vuex 的基本概念和原理

Vuex 是 Vue.js 的状态管理库,旨在为 Vue 应用提供集中式的状态管理。它通过一个全局的状态树来管理应用的所有组件共享的状态,使得状态的变化变得可预测和可追踪。

基本原理

  • State:保存应用的状态数据。
  • Getters:计算属性,基于 state 计算并返回值,能够在多个组件中共享。
  • Mutations:更改状态的唯一方法,必须是同步函数,直接修改 state。
  • Actions:可以包含异步操作,触发 mutations 来改变 state。
1.2 安装和配置 Vuex

首先需要安装 Vuex。

1
npm install vuex@next

然后在 Vue 应用中配置 Vuex。

代码示例

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
// store/index.js
import { createStore } from 'vuex';

const store = createStore({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
}
},
getters: {
doubleCount(state) {
return state.count * 2;
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment');
}, 1000);
}
}
});

export default store;

在主应用文件中引入并使用这个 store:

1
2
3
4
5
6
7
8
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import store from './store';

const app = createApp(App);
app.use(store);
app.mount('#app');

2. 状态管理

2.1 State, Getters, Mutations, Actions 的使用

在 Vuex 中,状态管理由 State、Getters、Mutations 和 Actions 四个部分组成。下面是它们的具体使用方法。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>
<h1>Count: {{ count }}</h1>
<h2>Double Count: {{ doubleCount }}</h2>
<button @click="increment">Increment</button>
<button @click="decrement">Decrement</button>
<button @click="incrementAsync">Increment Async</button>
</div>
</template>

<script>
import { mapState, mapGetters, mapActions } from 'vuex';

export default {
computed: {
...mapState(['count']),
...mapGetters(['doubleCount'])
},
methods: {
...mapActions(['increment', 'decrement', 'incrementAsync'])
}
}
</script>

在这个示例中:

  • 使用 mapState 来绑定 state 中的 count
  • 使用 mapGetters 绑定计算属性 doubleCount
  • 使用 mapActions 来绑定 incrementdecrementincrementAsync 方法。
2.2 具体实现的 Mutations 和 Actions

Mutations 是唯一可以直接修改 state 的方法:

1
2
3
4
5
6
7
8
mutations: {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
}
}

Actions 可以包含异步操作,使用 commit 触发 mutation:

1
2
3
4
5
6
7
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment');
}, 1000);
}
}

3. 模块化 Vuex

当应用的状态管理变得复杂时,可以将 store 拆分为多个模块。每个模块都有自己的 state、mutations、actions 和 getters。

代码示例

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
// store/modules/counter.js
const state = {
count: 0
};

const mutations = {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
}
};

const actions = {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment');
}, 1000);
}
};

const getters = {
doubleCount(state) {
return state.count * 2;
}
};

export default {
namespaced: true,
state,
mutations,
actions,
getters
};

在主 store 中引入模块:

1
2
3
4
5
6
7
8
9
10
11
// store/index.js
import { createStore } from 'vuex';
import counter from './modules/counter';

const store = createStore({
modules: {
counter
}
});

export default store;

在组件中使用模块化的 Vuex:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>
<h1>Count: {{ count }}</h1>
<h2>Double Count: {{ doubleCount }}</h2>
<button @click="increment">Increment</button>
<button @click="decrement">Decrement</button>
<button @click="incrementAsync">Increment Async</button>
</div>
</template>

<script>
import { mapState, mapGetters, mapActions } from 'vuex';

export default {
computed: {
...mapState('counter', ['count']),
...mapGetters('counter', ['doubleCount'])
},
methods: {
...mapActions('counter', ['increment', 'decrement', 'incrementAsync'])
}
}
</script>

在这个示例中,使用了 mapStatemapGettersmapActionsnamespace 参数来访问模块 counter 的状态和方法。

八、表单处理

1. 表单输入绑定

1.1 单向和双向数据绑定

在 Vue 中,表单输入可以通过单向和双向数据绑定来处理。

  • 单向数据绑定:数据从组件的 data 传递到 DOM 元素。即使 DOM 元素的值发生变化,它也不会更新组件的状态。

  • 双向数据绑定:使用 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
<template>
<div>
<h1>单向数据绑定</h1>
<input type="text" :value="message" @input="updateMessage" />
<p>输入的内容: {{ message }}</p>

<h1>双向数据绑定</h1>
<input type="text" v-model="message" />
<p>输入的内容: {{ message }}</p>
</div>
</template>

<script>
export default {
data() {
return {
message: ''
};
},
methods: {
updateMessage(event) {
this.message = event.target.value;
}
}
}
</script>

在这个示例中:

  • 第一部分演示了单向数据绑定,通过 :value@input 手动更新数据。
  • 第二部分使用 v-model 实现双向数据绑定,简化了数据管理。

2. 表单验证

2.1 基本验证方法

可以在 Vue 中通过编写自定义方法来实现简单的表单验证。

代码示例

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
<template>
<div>
<h1>表单验证示例</h1>
<form @submit.prevent="submitForm">
<div>
<label for="email">Email:</label>
<input type="email" v-model="email" />
<span v-if="emailError">{{ emailError }}</span>
</div>
<div>
<label for="password">Password:</label>
<input type="password" v-model="password" />
<span v-if="passwordError">{{ passwordError }}</span>
</div>
<button type="submit">提交</button>
</form>
</div>
</template>

<script>
export default {
data() {
return {
email: '',
password: '',
emailError: '',
passwordError: ''
};
},
methods: {
validateEmail() {
const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.email);
this.emailError = valid ? '' : '无效的邮箱地址';
},
validatePassword() {
this.passwordError = this.password.length < 6 ? '密码必须至少包含 6 个字符' : '';
},
submitForm() {
this.validateEmail();
this.validatePassword();

if (!this.emailError && !this.passwordError) {
alert('提交成功!');
}
}
}
}
</script>

在这个示例中,我们创建了一个简单的表单,其中包含邮箱和密码字段。提交表单时,会执行验证,并根据验证结果显示错误信息。

2.2 使用第三方库 (如 VeeValidate)

VeeValidate 是一个流行的 Vue 表单验证库,提供了更强大的功能和简化的验证方式。

安装 VeeValidate

1
npm install vee-validate@next @vee-validate/rules @vee-validate/i18n

代码示例

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>
<h1>VeeValidate 示例</h1>
<Form @submit="onSubmit">
<Field name="email" as="input" type="email" v-model="email" :rules="rules.email" />
<ErrorMessage name="email" />

<Field name="password" as="input" type="password" v-model="password" :rules="rules.password" />
<ErrorMessage name="password" />

<button type="submit">提交</button>
</Form>
</div>
</template>

<script>
import { Form, Field, ErrorMessage } from 'vee-validate';
import * as rules from '@vee-validate/rules';
import { defineRule } from 'vee-validate';

Object.keys(rules).forEach(rule => {
defineRule(rule, rules[rule]);
});

export default {
data() {
return {
email: '',
password: '',
rules: {
email: 'required|email',
password: 'required|min:6'
}
};
},
methods: {
onSubmit() {
alert('提交成功!');
}
}
}
</script>

在这个示例中,我们使用 VeeValidate 提供的 FormFieldErrorMessage 组件,定义了邮箱和密码的验证规则。通过 onSubmit 方法处理表单提交。

九、异步请求

1. AJAX 请求

1.1 使用 Axios 发送请求

Axios 是一个基于 Promise 的 HTTP 客户端,用于向后端发送请求。首先,你需要安装 Axios。

安装 Axios

1
npm install axios

然后在 Vue 组件中导入并使用 Axios 发送请求。

代码示例

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
<template>
<div>
<h1>获取用户数据</h1>
<button @click="fetchUsers">获取用户</button>
<ul>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
<p v-if="error">{{ error }}</p>
</div>
</template>

<script>
import axios from 'axios';

export default {
data() {
return {
users: [],
error: null
};
},
methods: {
async fetchUsers() {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/users');
this.users = response.data; // 处理响应数据
} catch (err) {
this.error = '无法获取用户数据'; // 处理错误
console.error(err);
}
}
}
}
</script>

在这个示例中:

  • 当用户点击“获取用户”按钮时,会调用 fetchUsers 方法。
  • 使用 axios.get 方法发送 GET 请求。
  • 如果请求成功,我们将返回的数据赋值给 users,如果请求失败,捕获错误并更新 error 状态。
1.2 处理响应和错误

在处理响应时,可以使用 response 对象的一些属性,例如 datastatusheaders。同时,处理错误时,可以根据错误的状态码来决定如何响应。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async fetchUsers() {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/users');
console.log('响应状态:', response.status); // 输出响应状态
console.log('响应头:', response.headers); // 输出响应头
this.users = response.data;
} catch (error) {
if (error.response) {
// 请求已发出,服务器回应的状态码不在 2xx 范围内
this.error = `错误 ${error.response.status}: ${error.response.data.message || '无法获取数据'}`;
} else if (error.request) {
// 请求已发出,但没有响应
this.error = '请求已发出,但没有响应';
} else {
// 其他错误
this.error = '请求发生错误: ' + error.message;
}
console.error(error);
}
}

2. 与后端 API 交互

2.1 获取数据

通过 Axios 可以轻松地与后端 API 进行交互,获取数据的过程如下:

代码示例

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
<template>
<div>
<h1>获取数据示例</h1>
<button @click="getData">获取数据</button>
<pre>{{ data }}</pre>
<p v-if="error">{{ error }}</p>
</div>
</template>

<script>
import axios from 'axios';

export default {
data() {
return {
data: null,
error: null
};
},
methods: {
async getData() {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
this.data = response.data; // 获取的数据
} catch (err) {
this.error = '数据获取失败';
console.error(err);
}
}
}
}
</script>

在这个示例中,点击按钮后,发送 GET 请求以获取帖子数据,并将结果存储在 data 中。

2.2 提交数据

除了获取数据,还可以使用 POST 请求提交数据。

代码示例

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
<template>
<div>
<h1>提交数据示例</h1>
<form @submit.prevent="submitData">
<input v-model="newPost.title" placeholder="标题" required />
<textarea v-model="newPost.body" placeholder="内容" required></textarea>
<button type="submit">提交</button>
</form>
<p v-if="responseMessage">{{ responseMessage }}</p>
<p v-if="error">{{ error }}</p>
</div>
</template>

<script>
import axios from 'axios';

export default {
data() {
return {
newPost: {
title: '',
body: ''
},
responseMessage: '',
error: null
};
},
methods: {
async submitData() {
try {
const response = await axios.post('https://jsonplaceholder.typicode.com/posts', this.newPost);
this.responseMessage = '数据提交成功: ' + response.data.id; // 响应中包含新建的帖子ID
} catch (err) {
this.error = '提交数据失败';
console.error(err);
}
}
}
}
</script>

在这个示例中:

  • 用户可以输入标题和内容并提交。
  • 使用 axios.post 方法将数据提交到服务器。
  • 成功响应后显示提交成功的消息。

十、性能优化

1. 性能分析

1.1 Vue DevTools 使用

Vue DevTools 是一个强大的工具,可以帮助你分析和调试 Vue 应用的性能。它可以监控组件的渲染时间、状态变化以及 Vuex 状态管理。

安装 Vue DevTools

  • 你可以在 Chrome 或 Firefox 浏览器中安装 Vue DevTools 插件。

使用 Vue DevTools

  1. 打开开发者工具,切换到 Vue 选项卡。
  2. 你可以查看组件树,监控每个组件的状态和属性。
  3. 使用“性能”选项卡来查看性能分析,查看哪些组件渲染时间较长,帮助定位性能瓶颈。
1.2 性能监测工具

除了 Vue DevTools,还可以使用其他性能监测工具,比如 Lighthouse(Chrome DevTools 内置)和 WebPageTest,这些工具能够帮助你评估整体应用性能,并提供优化建议。

  • Lighthouse:可以分析页面加载性能、可访问性、最佳实践和 SEO。
  • WebPageTest:可以详细报告页面加载时间、请求数和资源大小等信息。

2. 常见优化技巧

2.1 懒加载

懒加载是指在需要的时候再加载某些资源,可以显著减少初始加载时间。对于路由组件,Vue Router 支持懒加载。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/about',
component: () => import('./views/About.vue') // 懒加载
},
{
path: '/home',
component: () => import('./views/Home.vue') // 懒加载
}
]
});

在这个示例中,AboutHome 组件在访问时才会被加载,而不是在应用启动时加载。

2.2 组件缓存

使用 keep-alive 组件可以缓存不常变化的组件,避免重复渲染,提高性能。

代码示例

1
2
3
4
5
6
7
8
9
<template>
<div>
<router-view v-slot="{ Component }">
<keep-alive>
<Component />
</keep-alive>
</router-view>
</div>
</template>

在这个示例中,keep-alive 会缓存被包裹的组件,当你在不同路由之间切换时,不会重新渲染已缓存的组件。

2.3 服务器端渲染 (SSR)

服务器端渲染(SSR)可以提高首屏加载速度和 SEO 性能。Vue 3 提供了官方的 vue-server-renderer

使用 Nuxt.js 构建 SSR 应用
Nuxt.js 是一个基于 Vue 的框架,提供了开箱即用的 SSR 支持。

1
npx create-nuxt-app my-ssr-app

在创建项目时选择 SSR 模式,然后你可以在页面组件中直接编写 Vue 逻辑。Nuxt.js 将负责处理 SSR。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ content }}</p>
</div>
</template>

<script>
export default {
async asyncData() {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const post = await response.json();
return {
title: post.title,
content: post.body
};
}
}
</script>

在这个示例中,asyncData 方法在服务器端和客户端都可以执行,为页面提供初始数据。

十一、部署与发布

1. 构建项目

1.1 使用 Vue CLI 构建项目

如果你是使用 Vue CLI 创建的项目,可以通过以下命令构建项目:

1
npm run build

构建完成后,生成的文件会位于 dist 目录中。这些文件就是可以部署到服务器上的静态文件。

1.2 使用 Vite 构建项目

如果你是使用 Vite 创建的项目,同样可以通过以下命令构建项目:

1
npm run build

Vite 也会将生成的文件放在 dist 目录。

2. 部署到服务器

2.1 部署到静态文件托管服务
2.1.1 GitHub Pages
  1. 安装 gh-pages
    在项目根目录中安装 gh-pages 包:

    1
    npm install gh-pages --save-dev
  2. **修改 vue.config.js**(如果使用 Vue CLI):
    在根目录下创建或修改 vue.config.js 文件,设置 publicPath

    1
    2
    3
    module.exports = {
    publicPath: process.env.NODE_ENV === 'production' ? '/your-repo-name/' : '/'
    };
  3. 添加部署脚本
    package.json 文件的 scripts 部分添加以下部署命令:

    1
    2
    3
    "scripts": {
    "deploy": "npm run build && npx gh-pages -d dist"
    }
  4. 执行部署命令
    运行以下命令将项目部署到 GitHub Pages:

    1
    npm run deploy
2.1.2 Netlify
  1. 在 Netlify 创建帐户并登录
  2. 新建站点:选择“新建站点”,然后选择“从 Git 部署”。
  3. 连接至 GitHub:选择你的 GitHub 仓库。
  4. 配置构建设置
    • 构建命令:npm run build
    • 发布目录:dist
  5. 点击“部署站点”,待部署完成后,你会获得一个可以访问的 URL。
2.2 部署到传统服务器
  1. 构建项目
    使用上述方法构建项目,生成的 dist 目录包含了所有静态文件。

  2. 上传文件
    使用 FTP 客户端(如 FileZilla)或通过 SSH 将 dist 目录中的文件上传到你的服务器相应目录中(例如 /var/www/html)。

  3. 配置 Web 服务器
    确保你的 Web 服务器(如 Nginx 或 Apache)正确配置,以支持 Vue 应用的路由。例如,如果使用 Nginx,可能需要在配置文件中添加以下内容:

    1
    2
    3
    location / {
    try_files $uri $uri/ /index.html;
    }

    这样,所有的路由请求都会重定向到 index.html,以便 Vue Router 可以处理。

十二、进阶主题

1. TypeScript 支持

1.1 Vue 3 中使用 TypeScript 的最佳实践

Vue 3 原生支持 TypeScript,使用 TypeScript 可以帮助你提高代码的可维护性和可读性。下面是一些最佳实践:

  • 使用 Vue CLI 创建 TypeScript 项目
    通过 Vue CLI 创建项目时,可以选择 TypeScript 模板:
1
vue create my-project

在选择配置时,选择 TypeScript。

  • 定义组件类型
    在组件中,使用 defineComponent 来定义组件并使用类型注解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>{{ message }}</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
data() {
return {
message: 'Hello, TypeScript!'
};
}
});
</script>
  • 使用 props 类型
    使用 defineProps 来定义 Props 的类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
props: {
title: {
type: String,
required: true
}
},
setup(props) {
return () => <h1>{props.title}</h1>;
}
});
</script>

2. 自定义指令

2.1 创建和使用自定义指令

自定义指令可以让你在 DOM 元素上添加自定义行为。下面是如何创建和使用自定义指令的示例。

  • 创建自定义指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// main.js
import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);

app.directive('focus', {
// 当被绑定的元素插入到 DOM 中时...
mounted(el) {
el.focus(); // 聚焦元素
}
});

app.mount('#app');
  • 使用自定义指令
1
2
3
<template>
<input v-focus placeholder="我会自动聚焦" />
</template>

在这个示例中,我们创建了一个名为 v-focus 的自定义指令,当元素插入到 DOM 时会自动聚焦。

3. 插件开发

3.1 开发和安装 Vue 插件

Vue 插件可以扩展 Vue 的功能。下面是如何创建和使用一个插件的步骤。

  • 创建插件
1
2
3
4
5
6
7
8
// my-plugin.js
export default {
install(app) {
app.config.globalProperties.$log = (message) => {
console.log(message);
};
}
};
  • 安装插件
1
2
3
4
5
6
7
8
9
10
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import MyPlugin from './my-plugin';

const app = createApp(App);

app.use(MyPlugin); // 安装插件

app.mount('#app');
  • 使用插件
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<button @click="logMessage">点击我</button>
</template>

<script>
export default {
methods: {
logMessage() {
this.$log('Hello from Vue plugin!'); // 使用插件
}
}
};
</script>

在这个示例中,我们创建了一个简单的插件,提供了一个全局的 $log 方法来输出日志。

4. 使用第三方库

4.1 集成 Element Plus、Vuetify 等 UI 库

在 Vue 3 中,可以很方便地集成 UI 库。以下是如何使用 Element Plus 和 Vuetify 的示例。

4.1.1 集成 Element Plus
  1. 安装 Element Plus
1
npm install element-plus
  1. 在项目中导入 Element Plus
1
2
3
4
5
6
7
8
9
10
11
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import ElementPlus from 'element-plus';
import 'element-plus/lib/theme-chalk/index.css';

const app = createApp(App);

app.use(ElementPlus); // 使用 Element Plus

app.mount('#app');
  1. 使用 Element Plus 组件
1
2
3
<template>
<el-button type="primary">Element Plus 按钮</el-button>
</template>
4.1.2 集成 Vuetify
  1. 安装 Vuetify
1
npm install vuetify@next
  1. 在项目中导入 Vuetify
1
2
3
4
5
6
7
8
9
10
11
12
13
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import { createVuetify } from 'vuetify';
import 'vuetify/styles'; // 预先引入 Vuetify 样式

const vuetify = createVuetify(); // 创建 Vuetify 实例

const app = createApp(App);

app.use(vuetify); // 使用 Vuetify

app.mount('#app');
  1. 使用 Vuetify 组件
1
2
3
<template>
<v-btn color="primary">Vuetify 按钮</v-btn>
</template>

十三、项目实战

1. 项目概述

我们将创建一个 Todo List 应用,用户可以添加、删除和标记任务为完成。应用将使用 Vue 3、TypeScript、Vue Router、Vuex(状态管理)以及自定义指令等技术栈。

2. 项目结构

项目结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
todo-app/
├── public/
│ └── index.html
├── src/
│ ├── assets/
│ ├── components/
│ │ ├── TodoItem.vue
│ │ └── TodoList.vue
│ ├── store/
│ │ └── index.ts
│ ├── router/
│ │ └── index.ts
│ ├── App.vue
│ ├── main.ts
│ └── styles.css
└── package.json

3. 初始化项目

使用 Vue CLI 创建项目,并选择 TypeScript 和 Vue Router:

1
2
3
vue create todo-app
cd todo-app
vue add router

选择“使用历史模式”以避免 URL 中的哈希符号。

4. 配置 Vuex 状态管理

src/store/index.ts 中配置 Vuex:

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
import { createStore } from 'vuex';

export interface Todo {
id: number;
text: string;
completed: boolean;
}

export interface State {
todos: Todo[];
}

const store = createStore<State>({
state: {
todos: []
},
mutations: {
addTodo(state, text: string) {
const newTodo: Todo = {
id: Date.now(),
text,
completed: false
};
state.todos.push(newTodo);
},
toggleTodo(state, id: number) {
const todo = state.todos.find(todo => todo.id === id);
if (todo) {
todo.completed = !todo.completed;
}
},
removeTodo(state, id: number) {
state.todos = state.todos.filter(todo => todo.id !== id);
}
},
actions: {
addTodo({ commit }, text: string) {
commit('addTodo', text);
},
toggleTodo({ commit }, id: number) {
commit('toggleTodo', id);
},
removeTodo({ commit }, id: number) {
commit('removeTodo', id);
}
},
getters: {
allTodos: state => state.todos,
completedTodos: state => state.todos.filter(todo => todo.completed),
pendingTodos: state => state.todos.filter(todo => !todo.completed)
}
});

export default store;

5. 配置路由

src/router/index.ts 中配置路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { createRouter, createWebHistory } from 'vue-router';
import TodoList from '../components/TodoList.vue';

const routes = [
{
path: '/',
name: 'Home',
component: TodoList
}
];

const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
});

export default router;

6. 创建组件

6.1 TodoList.vue

src/components/TodoList.vue 中创建 Todo 列表和输入框:

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
<template>
<div>
<h1>Todo List</h1>
<input v-model="newTodo" @keypress.enter="addTodo" placeholder="添加新的任务" />
<ul>
<li v-for="todo in todos" :key="todo.id">
<TodoItem :todo="todo" @toggle="toggleTodo" @remove="removeTodo" />
</li>
</ul>
</div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';
import { useStore } from 'vuex';
import TodoItem from './TodoItem.vue';

export default defineComponent({
components: { TodoItem },
setup() {
const store = useStore();
const newTodo = ref('');

const addTodo = () => {
if (newTodo.value.trim()) {
store.dispatch('addTodo', newTodo.value);
newTodo.value = '';
}
};

const toggleTodo = (id: number) => {
store.dispatch('toggleTodo', id);
};

const removeTodo = (id: number) => {
store.dispatch('removeTodo', id);
};

return {
newTodo,
todos: store.getters.allTodos,
addTodo,
toggleTodo,
removeTodo
};
}
});
</script>

<style>
/* 适当的样式 */
input {
padding: 10px;
margin-bottom: 20px;
}
ul {
list-style: none;
padding: 0;
}
</style>
6.2 TodoItem.vue

src/components/TodoItem.vue 中创建每个 Todo 项目的显示:

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>
<span :class="{ completed: todo.completed }" @click="toggle">{{ todo.text }}</span>
<button @click="remove">删除</button>
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { Todo } from '../store';

export default defineComponent({
props: {
todo: {
type: Object as () => Todo,
required: true
}
},
emits: ['toggle', 'remove'],
setup(props, { emit }) {
const toggle = () => {
emit('toggle', props.todo.id);
};

const remove = () => {
emit('remove', props.todo.id);
};

return {
toggle,
remove
};
}
});
</script>

<style>
.completed {
text-decoration: line-through;
color: gray;
}
</style>

7. 主应用配置

src/App.vue 中配置主应用:

1
2
3
4
5
6
7
8
9
<template>
<router-view />
</template>

<script>
export default {
name: 'App'
};
</script>

8. 入口文件

src/main.ts 中配置入口文件:

1
2
3
4
5
6
7
8
9
10
import { createApp } from 'vue';
import App from './App.vue';
import store from './store';
import router from './router';
import './styles.css';

createApp(App)
.use(store)
.use(router)
.mount('#app');

9. 运行应用

在项目目录中运行以下命令以启动开发服务器:

1
npm run serve

打开浏览器,访问 http://localhost:8080(或控制台输出的地址),你应该能看到 Todo List 应用,能够添加、删除和标记任务。

结语

  • 学习资源
    • 官方文档
    • 视频教程
    • 社区和论坛