需求:
使用React实现一个Github用户搜索页面,通过输入Github的登录名(不包含中文)来搜索对应用户,并展示搜索到的用户头像和登录名。用户可以点击每个用户跳转到其主页。
细节要求:
①初次使用时,显示欢迎词:“欢迎使用,请输入登录名进行搜索”。
②搜索进行中,显示加载状态:“正在加载中,loading等”。
③搜索出现错误时,展示对应的错误信息。
④搜索结果为空时,显示“用户列表为空”。
最终效果
需求分析:
可将页面分为四部分:
①App组件:包裹Search,List组件
②Search组件:搜索Github用户。
③List组件:展示用户列表,包含Item组件。
④Item组件:展示每个用户的信息。
具体实现:
需要使用到PubsubJS实现兄弟组件的通信,需要先安装pubsub-js
npm install pubsub-js
还需要的使用到axios发送网路请求,需要先安装axios
npm install axios
目录解构如下
App.jsx
import { Component } from 'react'
import './app.css'
import List from './components/List'
import Search from './components/Search'
export default class App extends Component {
render() {
return (
<div className="app-container">
<Search />
<List />
</div>
)
}
}
app.css
.app-container {
width: 80%;
margin: 0 auto;
}
Search组件
index.jsx
import axios from 'axios'
import PubSub from 'pubsub-js'
import React, { Component } from 'react'
import './index.css'
export default class Search extends Component {
handleSearch = () => {
// 1.获取用户输入,连续解构赋值重命名,得到用户的输入keyWord
const {
keyWordNode: { value: keyWord }
} = this
PubSub.publish('updateState', { isFirst: false, isLoading: true })
// 2.发起网络请求
axios
.get('https://api.github.com/search/users?q=' + keyWord)
.then(res => {
// TODO 需要对数据进行校验
PubSub.publish('updateState', {
userList: res.data.items,
isLoading: false
})
})
.catch(err => {
PubSub.publish('updateState', {
err: err.msg
})
})
}
render() {
return (
<div className="search-container">
<div className="search-box">
<h2>搜索 Github 用户</h2>
<div>
<input ref={c => (this.keyWordNode = c)} type="text" placeholder="请输入用户名" />
<button onClick={this.handleSearch}>搜索</button>
</div>
</div>
</div>
)
}
}
index.css
.search-container {
height: 150px;
background-color: #bdc5cd;
border-radius: 5px;
position: relative;
}
.search-box {
position: absolute;
left: 50px;
bottom: 30px;
}
.search-box h2 {
margin: 10px 0;
}
.search-box input {
margin-right: 5px;
}
List组件
index.jsx
import Pubsub from 'pubsub-js'
import React, { Component } from 'react'
import Item from '../Item'
import './index.css'
export default class List extends Component {
state = {
userList: [], // 用户列表
isFirst: true, // 是否首次加载
isLoading: false, // 是否在加载
err: '' // 错误信息
}
// 页面挂载完成后订阅事件updateState, 更新state
componentDidMount() {
Pubsub.subscribe('updateState', (msg, stateObject) => {
this.setState(stateObject)
})
}
// 页面将要卸载时取消订阅事件updateState
componentWillUnmount() {
Pubsub.unsubscribe('updateState')
}
render() {
const { userList, isFirst, isLoading, err } = this.state
return
<div className="list-container">
{
isFirst ? <div>请输入用户登录名搜索</div> :
isLoading ? <div>正在加载中...</div> :
err ? <div style={{ color: 'red' }}>{err}</div> :
userList.length === 0 ? <div>用户列表为空</div> :
userList.map(item => <Item key={item.id} item={item} />)
}
</div>
}
}
index.css
.list-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 10px;
padding: 15px 0 0 0;
}
Item组件
import React, { Component } from 'react'
import './index.css'
export default class Item extends Component {
handleClickUser = item => {
return () => {
// 跳转到用户的github主页
window.open(item.html_url)
}
}
render() {
const { item } = this.props
return (
<div className="item-container" onClick={this.handleClickUser(item)}>
<img src={item.avatar_url} style={{ width: '80px', height: '80px', borderRadius: '5px' }} alt="" />
<span>{item.login}</span>
</div>
)
}
}
index.css
.item-container {
height: 120px;
background-color: #bdc5cd;
border-radius: 5px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.item-container:hover {
cursor: pointer;
background-color: rgba(241, 7, 7, 0.192);
}