在学习 Vue 组件化开发的过程中,我通过一个 GitHub 用户搜索的小案例,将组件拆分、事件通信、条件渲染以及异步请求等核心知识点串联了起来。虽然功能不复杂,但非常适合作为理解 Vue 应用结构的练习。
整个项目由三个组件组成:App.vue、MySearch.vue 和 MyList.vue,采用了典型的"父容器 + 功能组件"的结构设计。
一、根组件 App.vue:只负责组合,不写业务
先看根组件 App.vue:
xml
<template>
<div class="container">
<MySearch/>
<MyList/>
</div>
</template>
<script>
import MySearch from './components/MySearch.vue'
import MyList from './components/MyList.vue'
export default {
name: 'App',
components: {
MySearch,
MyList
}
}
</script>
可以看到,App.vue 没有任何业务逻辑,只做了三件事:
- 引入子组件
- 注册子组件
- 在模板中组合页面结构
这种写法非常符合 Vue 的设计思想:
根组件只负责"搭架子",具体功能全部下沉到子组件中。
二、MySearch.vue:搜索逻辑与请求发起者
1. 模板结构:输入 + 触发搜索
ini
<input
type="text"
placeholder="enter the name you search"
v-model.trim="keyWord"
@keyup.enter="searchUsers"
/>
<button @click="searchUsers">Search</button>
这里用到了几个非常关键的点:
v-model.trim:
自动去掉用户输入的首尾空格,避免无效请求@keyup.enter:
支持回车搜索,提升用户体验- 按钮点击和回车复用同一个方法
2. data 定义:只存搜索关键词
javascript
data() {
return {
keyWord: ''
}
}
搜索组件的职责非常单一:
👉 只关心"搜什么"与"怎么搜"
3. 核心方法:searchUsers
bash
methods: {
searchUsers() {
// 搜索前,通知列表进入 loading 状态
this.$bus.$emit('getSearchData', {
isFirst: false,
isLoading: true,
errMsg: '',
users: []
})
axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
response => {
this.$bus.$emit('getSearchData', {
isLoading: false,
users: response.data.items
})
},
error => {
this.$bus.$emit('getSearchData', {
isLoading: false,
errMsg: error.message,
users: []
})
}
)
}
}
这一段代码是整个项目的逻辑核心,可以总结为三步:
-
请求开始前
-
通知列表组件:
- 不再是首次进入
- 正在加载中
-
-
请求成功
- 将 GitHub 返回的用户数据发送给列表组件
-
请求失败
- 将错误信息传递给列表组件展示
注意:
搜索组件不直接修改列表组件的数据,而是通过事件总线"通知结果"。
三、MyList.vue:统一管理页面展示状态
1. 状态集中管理
yaml
data() {
return {
info: {
isFirst: true,
isLoading: false,
errMsg: '',
users: []
}
}
}
这里用一个 info 对象统一管理所有 UI 状态,非常关键:
- 是否首次进入
- 是否正在加载
- 是否有错误
- 是否有数据
👉 状态集中,模板判断才会清晰
2. 模板中的条件渲染
xml
<!-- 用户列表 -->
<div
class="card"
v-show="info.users.length"
v-for="user in info.users"
:key="user.login"
>
<a :href="user.html_url" target="_blank">
<img :src="user.avatar_url" style="width: 100px;" />
</a>
<p class="card-text">{{ user.login }}</p>
</div>
<h1 v-show="info.isFirst">欢迎!!!</h1>
<h1 v-show="info.isLoading">加载中...</h1>
<h1 v-show="info.errMsg">{{ info.errMsg }}</h1>
通过 v-show 控制不同状态下的界面展示:
- 初始 → 欢迎提示
- 搜索中 → 加载中
- 成功 → 用户列表
- 失败 → 错误信息
这正是真实业务中最常见的 UI 状态切换场景。
3. 接收搜索组件的数据(事件总线)
javascript
mounted() {
this.$bus.$on('getSearchData', datas => {
this.info = { ...this.info, ...datas }
})
}
这里的写法非常值得学习:
- 使用展开运算符合并状态
- 只更新传过来的字段
- 保留原有未修改状态
👉 这是一个低耦合、可维护性很强的写法。
四、为什么要用事件总线?
在这个项目中:
MySearch和MyList是兄弟组件- 它们之间没有直接的父子 props 关系
使用事件总线的好处是:
- 不需要层层传 props
- 组件之间完全解耦
- 非常适合小型项目和学习阶段
虽然在大型项目中会使用 Vuex / Pinia,但在这里,事件总线是一个非常合适的选择。
五、总结
通过这个 GitHub 用户搜索案例,我对 Vue 的理解不再停留在指令和语法层面,而是开始关注:
- 组件应该如何拆分
- 数据应该由谁维护
- 不同状态下页面如何变化
- 组件之间如何通信才合理