钱途似金 發表於 2025-12-2 14:12:00

完整教程:Vue-github 用户搜索案例

<style>pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-size: 14px !important; line-height: 1.6 !important; padding: 16px !important; margin: 16px 0 !important; background-color: rgba(248, 248, 248, 1) !important; border: 1px solid rgba(225, 228, 232, 1) !important; border-radius: 6px !important; tab-size: 4 !important; -moz-tab-size: 4 !important; max-width: 100% !important; box-sizing: border-box !important }
code { font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-size: 14px !important; white-space: pre !important; word-wrap: normal !important; word-break: normal !important; overflow-wrap: normal !important; display: inline !important; background: rgba(0, 0, 0, 0) !important; border: none !important; padding: 0 !important; margin: 0 !important; line-height: inherit !important }
pre code { background: rgba(0, 0, 0, 0) !important; border: 0 !important; border-radius: 0 !important; display: block !important; line-height: 1.6 !important; margin: 0 !important; max-width: none !important; overflow: visible !important; padding: 0 !important; white-space: pre !important; word-wrap: normal !important; word-break: normal !important; color: inherit !important }
.token.comment, .token.prolog, .token.doctype, .token.cdata { color: rgba(112, 128, 144, 1) !important; font-style: italic !important }
.token.punctuation { color: rgba(153, 153, 153, 1) !important }
.token.atrule, .token.attr-value, .token.keyword { color: rgba(0, 119, 170, 1) !important; font-weight: bold !important }
.token.function, .token.class-name { color: rgba(221, 74, 104, 1) !important; font-weight: bold !important }
.token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: rgba(102, 153, 0, 1) !important }
.token.property, .token.tag, .token.boolean, .token.number, .token.constant, .token.symbol, .token.deleted { color: rgba(153, 0, 85, 1) !important }
.cnblogs-markdown pre, .cnblogs-post-body pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; background-color: rgba(248, 248, 248, 1) !important; border: 1px solid rgba(225, 228, 232, 1) !important; border-radius: 6px !important; padding: 16px !important; margin: 16px 0 !important }
pre, pre, pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important }</style>
      <div class="htmledit_views atom-one-dark" id="content_views"><h3>一、前言</h3><p>还在学 Vue 的基础语法?学完 <code>v-model</code>、<code>v-for</code>、<code>v-if</code> 却不知道如何整合成一个完整项目?</p><p>别担心!本文带你从零开始,<strong>用 2 小时完成一个真实的 Vue 项目</strong> —— <strong>GitHub 用户搜索系统</strong>。</p><p>✅ 你将学到:</p><ul><li>如何使用&nbsp;<code>axios</code>&nbsp;发送网络请求</li><li>组件化开发与父子/兄弟组件通信</li><li>使用&nbsp;<strong>全局事件总线(Global Event Bus)</strong>&nbsp;实现跨组件数据传递</li><li>处理加载状态与错误提示</li><li>项目结构组织与代码规范</li></ul><blockquote><p> 本项目不依赖 Vue Router 或 Vuex,纯原生 Vue + Axios,适合初学者快速上手!</p></blockquote><h3></h3><h3>二、项目演示</h3><p>功能说明:</p><ul><li>输入用户名关键词,点击搜索</li><li>显示加载中动画</li><li>展示用户头像、用户名、主页链接</li><li>支持点击头像跳转到 GitHub 主页</li><li>搜索失败时显示错误信息</li><li>初始页面显示欢迎语</li></ul><h3></h3><h3>三、涉及知识点</h3><table><thead><tr><th>知识点</th><th>说明</th></tr></thead><tbody><tr><td>Vue 基础</td><td>模板语法、v-model、v-for、v-show、生命周期</td></tr><tr><td>Axios</td><td>发送 HTTP 请求获取 GitHub 用户数据</td></tr><tr><td>全局事件总线</td><td>实现&nbsp;<code>Search</code>&nbsp;与&nbsp;<code>List</code>&nbsp;组件通信</td></tr><tr><td>组件化开发</td><td>拆分为&nbsp;<code>App.vue</code>、<code>Search.vue</code>、<code>List.vue</code></td></tr><tr><td>GitHub Open API</td><td>免费接口,无需认证</td></tr></tbody></table><h3></h3><h3>四、项目准备</h3><h4>1. 创建 Vue 项目</h4>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="language-bash"># 使用 Vue CLI(Vue 2)
vue create github-search
# 或使用 Vite(Vue 3)
npm create vue@latest github-search</code></pre>
<blockquote><p>本文以 Vue 2 + Vue CLI 为例,Vue 3 写法类似。</p></blockquote><hr><h4>2. 安装依赖</h4>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="language-bash"># 安装 axios(发送请求)
npm install axios
# 可选:安装 bootstrap 美化界面
npm install bootstrap</code></pre>
<hr><h4>3. 引入 Bootstrap(可选)</h4><p>在 <code>main.js</code> 中引入:</p>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="language-javascript">import 'bootstrap/dist/css/bootstrap.css'</code></pre>
<h3></h3><h3>五、项目结构</h3>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>src/
├── App.vue
├── components/
│   ├── Search.vue      # 搜索组件
│   └── List.vue      # 列表展示组件
├── eventBus.js         # 全局事件总线
└── main.js</code></pre>
<h3></h3><h3>六、核心代码实现</h3><h4>1. 创建全局事件总线&nbsp;<code>eventBus.js</code></h4>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="language-javascript">// src/eventBus.js
import Vue from 'vue'
export default new Vue()</code></pre>
<blockquote><p> 作用:作为“中转站”,实现兄弟组件通信。</p></blockquote><hr><h4>2.&nbsp;<code>App.vue</code>&nbsp;—— 父组件(整合子组件)</h4>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="language-html"><template>
<div class="container">
    <h1 class="text-center my-4"> GitHub 用户搜索</h1>
    <search>
    <list>
</list></search></div>
</template>
&lt;script&gt;
import Search from './components/Search.vue'
import List from './components/List.vue'
export default {
name: 'App',
components: {
    Search,
    List
}
}
&lt;/script&gt;
<style>.container { max-width: 900px; margin: 0 auto; padding: 20px }</style></code></pre>
<hr><h4>3.&nbsp;<code>Search.vue</code>&nbsp;—— 搜索组件(发送请求)</h4>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="language-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>
&lt;script&gt;
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 =&gt; {
          // 请求成功
          const users = response.data.items
          eventBus.$emit('updateList', {
            isLoading: false,
            users
          })
      })
      .catch(error =&gt; {
          // 请求失败
          eventBus.$emit('updateList', {
            isLoading: false,
            errMsg: error.message
          })
      })
    }
}
}
&lt;/script&gt;
<style scoped="">.jumbotron { background-color: rgba(248, 249, 250, 1); padding: 2rem; border-radius: 10px; margin-bottom: 20px }
input { width: 60%; padding: 8px; border: 1px solid rgba(221, 221, 221, 1); border-radius: 4px }</style></code></pre>
<hr><h4>4.&nbsp;<code>List.vue</code>&nbsp;—— 列表组件(展示数据)</h4>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="language-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">
      
      <img :src="user.avatar_url" alt="头像" style="width: 100%; height: 200px; object-fit: cover">
      
      <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>
&lt;script&gt;
import eventBus from '../eventBus'
export default {
name: 'List',
data() {
    return {
      info: {
      isFirst: true,   // 是否为初始状态
      isLoading: false,// 是否正在加载
      errMsg: '',      // 错误信息
      users: []          // 用户列表
      }
    }
},
mounted() {
    // 接收 Search 组件发送的数据
    eventBus.$on('updateList', (data) =&gt; {
      // 合并对象
      this.info = { ...this.info, ...data }
    })
},
// 组件销毁前解绑事件,防止内存泄漏
beforeDestroy() {
    eventBus.$off('updateList')
}
}
&lt;/script&gt;
<style scoped="">.row { margin: 0 }
.card { margin-bottom: 1rem; text-align: center; border: none }
.card img { border-radius: 8px }
.welcome { color: rgba(102, 102, 102, 1); font-size: 1.2rem; margin-top: 50px }</style></code></pre>
<h3></h3><h3>七、运行项目</h3>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="language-bash">npm run serve</code></pre>
<p>访问 <code>http://localhost:8080</code>,输入用户名如 <code>tom</code>、<code>john</code>,即可看到搜索结果!</p><h3></h3><h3>八、常见问题与优化建议</h3><h4>❌ 问题 1:事件总线未解绑导致内存泄漏?</h4><p>✅ <strong>解决</strong>:在 <code>beforeDestroy</code> 中使用 <code>$off</code> 解绑:</p>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="language-javascript">beforeDestroy() {
eventBus.$off('updateList')
}</code></pre>
<hr><h4>❌ 问题 2:输入框回车搜索?</h4><p>✅ 已在 <code>v-model</code> 上添加 <code>@keyup.enter="searchUsers"</code>,支持回车触发。</p><hr><h4>✅ 优化建议</h4><table><thead><tr><th>优化点</th><th>说明</th></tr></thead><tbody><tr><td>防抖处理</td><td>频繁输入时可添加&nbsp;<code>lodash.debounce</code>&nbsp;防抖</td></tr><tr><td>图片懒加载</td><td>安装&nbsp;<code>vue-lazyload</code>&nbsp;插件,提升性能</td></tr><tr><td>错误重试</td><td>增加重试按钮,提升用户体验</td></tr><tr><td>样式美化</td><td>使用 Element UI / Ant Design Vue 提升 UI</td></tr></tbody></table><h3></h3><h3>九、扩展:Vue 3 写法差异(Composition API)</h3><p>如果你使用 Vue 3 + <code>setup</code>,<code>List.vue</code> 可改为:</p>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code class="language-javascript">import { onMounted, onBeforeUnmount } from 'vue'
export default {
setup() {
    const info = reactive({
      isFirst: true,
      isLoading: false,
      errMsg: '',
      users: []
    })
    onMounted(() =&gt; {
      eventBus.on('updateList', (data) =&gt; {
      Object.assign(info, data)
      })
    })
    onBeforeUnmount(() =&gt; {
      eventBus.off('updateList')
    })
    return { info }
}
}</code></pre>
<h3></h3><h3>十、总结</h3><table><thead><tr><th>组件</th><th>职责</th></tr></thead><tbody><tr><td><code>App.vue</code></td><td>整合组件,搭建页面结构</td></tr><tr><td><code>Search.vue</code></td><td>用户输入、发送请求、通知状态</td></tr><tr><td><code>List.vue</code></td><td>接收数据、展示列表、处理 UI 状态</td></tr><tr><td><code>eventBus.js</code></td><td>兄弟组件通信桥梁</td></tr></tbody></table><p>✅ <strong>项目收获</strong>:</p><ul><li>掌握了 Vue 组件化开发流程</li><li>学会了使用 Axios 请求数据</li><li>理解了全局事件总线的通信机制</li><li>提升了错误处理与用户体验意识</li></ul><h3></h3><h3>十一、结语</h3><p>感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!</p></div><br><br>
来源:https://www.cnblogs.com/yangykaifa/p/19297504
頁: [1]
查看完整版本: 完整教程:Vue-github 用户搜索案例