一、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 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 库和工具。
安装步骤 :
下载 Node.js :访问 Node.js 官方网站 ,根据你的操作系统(Windows、macOS 或 Linux)下载合适的安装包。
安装 Node.js :运行下载的安装包,并按照提示完成安装。安装过程中请确保选中“Add to PATH”选项,这样可以在命令行中直接使用 node
和 npm
命令。
验证安装 :安装完成后,可以打开命令行(终端)并输入以下命令来验证安装是否成功:
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 :
在命令行中执行以下命令安装 Vue CLI:
验证安装:
创建 Vue 3 项目 :
使用 CLI 创建新项目:
在创建过程中,会提示选择配置,可以选择默认配置或手动选择特性(如 TypeScript、Vue Router、Vuex 等)。
进入项目目录:
启动开发服务器:
1.2.2 使用 Vite Vite 是一个新兴的前端构建工具,提供更快速的构建和热重载体验,适合小型和中型项目。
创建 Vue 3 项目 :
在命令行中执行以下命令创建新项目:1 npm init vite@latest my-vue-app -- --template vue
进入项目目录:
安装依赖:
启动开发服务器:
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 import { createApp } from 'vue' ;const app = createApp ({ data ( ) { return { message : 'Hello Vue!' }; } }); 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
是一个计算属性,它根据 firstName
和 lastName
的值动态生成完整的名字。
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 <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 <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 <template > <div > <Child :name ="parentName" /> </div > </template > <script > import Child from './Child.vue' ;export default { components : { Child }, data ( ) { return { parentName : 'Parent Component' }; } } </script > <template > <div > <p > Received from parent: {{ name }}</p > </div > </template > <script > export default { props : { name : String } } </script >
在这个示例中,Parent.vue
通过 props
向 Child.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 <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 > <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 <template > <div > <Parent /> </div > </template > <script > import Parent from './Parent.vue' ;export default { components : { Parent }, provide ( ) { return { sharedData : 'Data from Grandparent' }; } } </script > <template > <Child /> </template > <script > import Child from './Child.vue' ;export default { components : { Child } } </script > <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 <template > <Child > <p > This is content from the parent.</p > </Child > </template > <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 <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 > <template > <div > <slot name ="header" > </slot > <p > This is the main content.</p > <slot name ="footer" > </slot > </div > </template >
在这个示例中,Parent.vue
为 Child.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 <template > <Child > <template v-slot:default ="{ msg }" > <p > {{ msg }}</p > </template > </Child > </template > <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()
函数中定义了一个响应式的 message
和 count
,并创建了一个 increment
方法来增加计数。
2. 响应式 API 2.1 ref
和 reactive
的使用
**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 ( ) { const user = reactive ({ name : 'John Doe' , age : 25 }); const updateAge = ( ) => { user.age ++; }; return { user, updateAge }; } } </script >
在这个示例中,我们使用 reactive
创建了一个 user
对象,包含 name
和 age
属性,并提供了一个方法来增加年龄。
2.2 computed
和 watch
的使用
**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 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 <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 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 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 const routes = [ { path : '/' , name : 'Home' , component : Home , children : [ { path : 'profile' , name : 'Profile' , component : () => import ('../views/Profile.vue' ) } ] } ];
在这个示例中,Profile
是 Home
的嵌套路由,当用户访问 /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.beforeEach ((to, from , next ) => { const isAuthenticated = false ; if (to.name !== 'Home' && !isAuthenticated) { next ({ name : 'Home' }); } else { next (); } });
在这个示例中,如果用户未登录(即 isAuthenticated
为 false
),且试图访问不是首页的路由,则会被重定向到首页。
3. 路由参数 3.1 动态路由和查询参数 动态路由允许在路径中定义参数,例如用户 ID。查询参数则可以通过 URL 传递额外的数据。
代码示例 :
1 2 3 4 5 6 7 8 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/123
,User.vue
会显示 User ID: 123
。
3.2 查询参数 查询参数在 URL 中以 ?
开头,后面跟着键值对。例如:/user?id=123
。
代码示例 :
1 2 3 4 5 6 7 8 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 ; }); return { userId }; } } </script >
在这个示例中,访问 /user?id=123
,User.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。
然后在 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 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 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
来绑定 increment
、decrement
和 incrementAsync
方法。
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 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 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 >
在这个示例中,使用了 mapState
、mapGetters
和 mapActions
的 namespace
参数来访问模块 counter
的状态和方法。
八、表单处理 1. 表单输入绑定 1.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 <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 提供的 Form
、Field
和 ErrorMessage
组件,定义了邮箱和密码的验证规则。通过 onSubmit
方法处理表单提交。
九、异步请求 1. AJAX 请求 1.1 使用 Axios 发送请求 Axios 是一个基于 Promise 的 HTTP 客户端,用于向后端发送请求。首先,你需要安装 Axios。
安装 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
对象的一些属性,例如 data
、status
和 headers
。同时,处理错误时,可以根据错误的状态码来决定如何响应。
代码示例 :
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 ) { 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 ; } catch (err) { this .error = '提交数据失败' ; console .error (err); } } } } </script >
在这个示例中:
用户可以输入标题和内容并提交。
使用 axios.post
方法将数据提交到服务器。
成功响应后显示提交成功的消息。
十、性能优化 1. 性能分析 Vue DevTools 是一个强大的工具,可以帮助你分析和调试 Vue 应用的性能。它可以监控组件的渲染时间、状态变化以及 Vuex 状态管理。
安装 Vue DevTools :
你可以在 Chrome 或 Firefox 浏览器中安装 Vue DevTools 插件。
使用 Vue DevTools :
打开开发者工具,切换到 Vue 选项卡。
你可以查看组件树,监控每个组件的状态和属性。
使用“性能”选项卡来查看性能分析,查看哪些组件渲染时间较长,帮助定位性能瓶颈。
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' ) } ] });
在这个示例中,About
和 Home
组件在访问时才会被加载,而不是在应用启动时加载。
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 创建的项目,可以通过以下命令构建项目:
构建完成后,生成的文件会位于 dist
目录中。这些文件就是可以部署到服务器上的静态文件。
1.2 使用 Vite 构建项目 如果你是使用 Vite 创建的项目,同样可以通过以下命令构建项目:
Vite 也会将生成的文件放在 dist
目录。
2. 部署到服务器 2.1 部署到静态文件托管服务 2.1.1 GitHub Pages
安装 gh-pages : 在项目根目录中安装 gh-pages
包:
1 npm install gh-pages --save-dev
**修改 vue.config.js
**(如果使用 Vue CLI): 在根目录下创建或修改 vue.config.js
文件,设置 publicPath
:
1 2 3 module .exports = { publicPath : process.env .NODE_ENV === 'production' ? '/your-repo-name/' : '/' };
添加部署脚本 : 在 package.json
文件的 scripts
部分添加以下部署命令:
1 2 3 "scripts" : { "deploy" : "npm run build && npx gh-pages -d dist" }
执行部署命令 : 运行以下命令将项目部署到 GitHub Pages:
2.1.2 Netlify
在 Netlify 创建帐户并登录 。
新建站点 :选择“新建站点”,然后选择“从 Git 部署”。
连接至 GitHub :选择你的 GitHub 仓库。
配置构建设置 :
构建命令:npm run build
发布目录:dist
点击“部署站点” ,待部署完成后,你会获得一个可以访问的 URL。
2.2 部署到传统服务器
构建项目 : 使用上述方法构建项目,生成的 dist
目录包含了所有静态文件。
上传文件 : 使用 FTP 客户端(如 FileZilla)或通过 SSH 将 dist
目录中的文件上传到你的服务器相应目录中(例如 /var/www/html
)。
配置 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 模板:
在选择配置时,选择 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 import { createApp } from 'vue' ;import App from './App.vue' ;const app = createApp (App );app.directive ('focus' , { 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 export default { install (app ) { app.config .globalProperties .$log = (message ) => { console .log (message); }; } };
1 2 3 4 5 6 7 8 9 10 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
安装 Element Plus :
1 npm install element-plus
在项目中导入 Element Plus :
1 2 3 4 5 6 7 8 9 10 11 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 ); app.mount ('#app' );
使用 Element Plus 组件 :
1 2 3 <template > <el-button type ="primary" > Element Plus 按钮</el-button > </template >
4.1.2 集成 Vuetify
安装 Vuetify :
1 npm install vuetify@next
在项目中导入 Vuetify :
1 2 3 4 5 6 7 8 9 10 11 12 13 import { createApp } from 'vue' ;import App from './App.vue' ;import { createVuetify } from 'vuetify' ;import 'vuetify/styles' ; const vuetify = createVuetify (); const app = createApp (App );app.use (vuetify); app.mount ('#app' );
使用 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-appvue 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. 运行应用 在项目目录中运行以下命令以启动开发服务器:
打开浏览器,访问 http://localhost:8080
(或控制台输出的地址),你应该能看到 Todo List 应用,能够添加、删除和标记任务。
结语