一、前言
还在学 Vue 的基础语法?学完 v-model、v-for、v-if 却不知道如何整合成一个完整项目?
别担心!本文带你从零开始,用 2 小时完成一个真实的 Vue 项目 ------ GitHub 用户搜索系统。
✅ 你将学到:
- 如何使用
axios发送网络请求 - 组件化开发与父子/兄弟组件通信
- 使用 全局事件总线(Global Event Bus) 实现跨组件数据传递
- 处理加载状态与错误提示
- 项目结构组织与代码规范
💡 本项目不依赖 Vue Router 或 Vuex,纯原生 Vue + Axios,适合初学者快速上手!
二、项目演示
功能说明:
- 输入用户名关键词,点击搜索
- 显示加载中动画
- 展示用户头像、用户名、主页链接
- 支持点击头像跳转到 GitHub 主页
- 搜索失败时显示错误信息
- 初始页面显示欢迎语
三、涉及知识点
| 知识点 | 说明 |
|---|---|
| Vue 基础 | 模板语法、v-model、v-for、v-show、生命周期 |
| Axios | 发送 HTTP 请求获取 GitHub 用户数据 |
| 全局事件总线 | 实现 Search 与 List 组件通信 |
| 组件化开发 | 拆分为 App.vue、Search.vue、List.vue |
| GitHub Open API | 免费接口,无需认证 |
四、项目准备
1. 创建 Vue 项目
bash
# 使用 Vue CLI(Vue 2)
vue create github-search
# 或使用 Vite(Vue 3)
npm create vue@latest github-search
本文以 Vue 2 + Vue CLI 为例,Vue 3 写法类似。
2. 安装依赖
bash
# 安装 axios(发送请求)
npm install axios
# 可选:安装 bootstrap 美化界面
npm install bootstrap
3. 引入 Bootstrap(可选)
在 main.js 中引入:
javascript
import 'bootstrap/dist/css/bootstrap.css'
五、项目结构
src/
├── App.vue
├── components/
│ ├── Search.vue # 搜索组件
│ └── List.vue # 列表展示组件
├── eventBus.js # 全局事件总线
└── main.js
六、核心代码实现
1. 创建全局事件总线 eventBus.js
javascript
// src/eventBus.js
import Vue from 'vue'
export default new Vue()
📌 作用:作为"中转站",实现兄弟组件通信。
2. App.vue ------ 父组件(整合子组件)
html
<template>
<div class="container">
<h1 class="text-center my-4">🔍 GitHub 用户搜索</h1>
<Search />
<List />
</div>
</template>
<script>
import Search from './components/Search.vue'
import List from './components/List.vue'
export default {
name: 'App',
components: {
Search,
List
}
}
</script>
<style>
.container {
max-width: 900px;
margin: 0 auto;
padding: 20px;
}
</style>
3. Search.vue ------ 搜索组件(发送请求)
html
<template>
<section class="jumbotron">
<h3 class="jumbotron-heading">Search GitHub Users</h3>
<div>
<input
type="text"
placeholder="请输入要搜索的用户名"
v-model="keyWord"
@keyup.enter="searchUsers"
/>
<button @click="searchUsers" class="btn btn-primary">搜索</button>
</div>
</section>
</template>
<script>
import axios from 'axios'
import eventBus from '../eventBus'
export default {
name: 'Search',
data() {
return {
keyWord: '' // 搜索关键词
}
},
methods: {
searchUsers() {
// 1. 搜索前:通知 List 组件更新状态(加载中)
eventBus.$emit('updateList', {
isFirst: false,
isLoading: true,
errMsg: '',
users: []
})
// 2. 发送请求
const url = `https://api.github.com/search/users?q=${this.keyWord}`
axios.get(url)
.then(response => {
// 请求成功
const users = response.data.items
eventBus.$emit('updateList', {
isLoading: false,
users
})
})
.catch(error => {
// 请求失败
eventBus.$emit('updateList', {
isLoading: false,
errMsg: error.message
})
})
}
}
}
</script>
<style scoped>
.jumbotron {
background-color: #f8f9fa;
padding: 2rem;
border-radius: 10px;
margin-bottom: 20px;
}
input {
width: 60%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
</style>
4. List.vue ------ 列表组件(展示数据)
html
<template>
<div class="row">
<!-- 用户列表 -->
<div
v-for="user in info.users"
:key="user.login"
class="card col-lg-3 col-md-4 col-sm-6 p-0"
>
<a :href="user.html_url" target="_blank">
<img :src="user.avatar_url" alt="头像" style="width: 100%; height: 200px; object-fit: cover;">
</a>
<p class="card-text text-center">{{ user.login }}</p>
</div>
<!-- 欢迎语 -->
<h2 v-show="info.isFirst" class="welcome text-center">欢迎使用 GitHub 搜索系统!</h2>
<!-- 加载中 -->
<h2 v-show="info.isLoading" class="text-center">🔍 搜索中,请稍候...</h2>
<!-- 错误信息 -->
<h2 v-show="info.errMsg" class="text-danger text-center">❌ {{ info.errMsg }}</h2>
</div>
</template>
<script>
import eventBus from '../eventBus'
export default {
name: 'List',
data() {
return {
info: {
isFirst: true, // 是否为初始状态
isLoading: false, // 是否正在加载
errMsg: '', // 错误信息
users: [] // 用户列表
}
}
},
mounted() {
// 接收 Search 组件发送的数据
eventBus.$on('updateList', (data) => {
// 合并对象
this.info = { ...this.info, ...data }
})
},
// 组件销毁前解绑事件,防止内存泄漏
beforeDestroy() {
eventBus.$off('updateList')
}
}
</script>
<style scoped>
.row {
margin: 0;
}
.card {
margin-bottom: 1rem;
text-align: center;
border: none;
}
.card img {
border-radius: 8px;
}
.welcome {
color: #666;
font-size: 1.2rem;
margin-top: 50px;
}
</style>
七、运行项目
bash
npm run serve
访问 http://localhost:8080,输入用户名如 tom、john,即可看到搜索结果!
八、常见问题与优化建议
❌ 问题 1:事件总线未解绑导致内存泄漏?
✅ 解决 :在 beforeDestroy 中使用 $off 解绑:
javascript
beforeDestroy() {
eventBus.$off('updateList')
}
❌ 问题 2:输入框回车搜索?
✅ 已在 v-model 上添加 @keyup.enter="searchUsers",支持回车触发。
✅ 优化建议
| 优化点 | 说明 |
|---|---|
| 防抖处理 | 频繁输入时可添加 lodash.debounce 防抖 |
| 图片懒加载 | 安装 vue-lazyload 插件,提升性能 |
| 错误重试 | 增加重试按钮,提升用户体验 |
| 样式美化 | 使用 Element UI / Ant Design Vue 提升 UI |
九、扩展:Vue 3 写法差异(Composition API)
如果你使用 Vue 3 + setup,List.vue 可改为:
javascript
import { onMounted, onBeforeUnmount } from 'vue'
export default {
setup() {
const info = reactive({
isFirst: true,
isLoading: false,
errMsg: '',
users: []
})
onMounted(() => {
eventBus.on('updateList', (data) => {
Object.assign(info, data)
})
})
onBeforeUnmount(() => {
eventBus.off('updateList')
})
return { info }
}
}
十、总结
| 组件 | 职责 |
|---|---|
App.vue |
整合组件,搭建页面结构 |
Search.vue |
用户输入、发送请求、通知状态 |
List.vue |
接收数据、展示列表、处理 UI 状态 |
eventBus.js |
兄弟组件通信桥梁 |
✅ 项目收获:
- 掌握了 Vue 组件化开发流程
- 学会了使用 Axios 请求数据
- 理解了全局事件总线的通信机制
- 提升了错误处理与用户体验意识
十一、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!