构建基于 React Router 与 Superagent 的前端路由系统
项目入口与路由配置
在单页应用(SPA)的架构中,入口文件负责初始化 DOM 节点并挂载根组件。本项目采用 react-router 管理前端路由,通过哈希历史(Hash History)模式实现无刷新页面跳转。以下配置定义了根路径与详情路径的映射关系,并在路由更新时重置页面滚动位置。
// src/main.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route } from 'react-router';
import createHashHistory from 'history/lib/createHashHistory';
import RepositoryList from './components/RepositoryList';
import RepositoryDetail from './components/RepositoryDetail';
const history = createHashHistory({ queryKey: false });
ReactDOM.render(
<Router history={history} onUpdate={() => window.scrollTo(0, 0)}>
<Route path="/" component={RepositoryList} />
<Route path="/detail/:repoName" component={RepositoryDetail} />
</Router>,
document.getElementById('root')
);
路由配置中,/ 路径指向列表组件,而 /detail/:repoName 则是一个动态路由,允许将仓库名称作为参数传递给详情组件。
仓库列表组件实现
列表组件主要负责展示可用的仓库选项,并通过 Link 组件实现声明式导航。当用户点击链接时,路由会自动切换至对应的详情页面。
// components/RepositoryList.js
import React from 'react';
import { Link } from 'react-router';
class RepositoryList extends React.Component {
render() {
return (
<div className="list-container">
<h2>请选择一个代码仓库</h2>
<ul>
<li><Link to="/detail/react">React Core</Link></li>
<li><Link to="/detail/react-native">React Native</Link></li>
<li><Link to="/detail/jest">Jest Framework</Link></li>
</ul>
</div>
);
}
}
export default RepositoryList;
详情组件与数据交互
详情组件需要处理 URL 参数,并根据参数向后端发起异步请求。这里使用 superagent 库进行 HTTP 通信。组件内部维护了多种数据类型(如提交记录、分支、拉取请求)的状态,并通过切换状态码来控制渲染内容。
// components/RepositoryDetail.js
import React from 'react';
import request from 'superagent';
class RepositoryDetail extends React.Component {
constructor(props) {
super(props);
this.state = {
activeView: 'commits',
commits: [],
forks: [],
pulls: []
};
// 绑定事件处理函数
this.handleViewChange = this.handleViewChange.bind(this);
}
componentDidMount() {
const { repoName } = this.props.params;
const apiBase = 'http://192.168.1.100:3000';
// 并行获取多种类型的数据
this.fetchData(`${apiBase}/${repoName}/commits`, 'commits');
this.fetchData(`${apiBase}/${repoName}/forks`, 'forks');
this.fetchData(`${apiBase}/${repoName}/pulls`, 'pulls');
}
fetchData(url, dataType) {
request.get(url).end((err, res) => {
if (!err && res.body) {
this.setState({ [dataType]: res.body });
} else {
console.error(`Failed to load ${dataType}:`, err);
}
});
}
handleViewChange(viewType) {
this.setState({ activeView: viewType });
}
renderContent() {
const { activeView, commits, forks, pulls } = this.state;
const data = activeView === 'commits' ? commits
: activeView === 'forks' ? forks
: pulls;
return data.map((item, idx) => {
const author = item.author || item.owner || 'Unknown';
return (
<p key={idx}>
<strong>{author}</strong>:
<a href={item.url} target="_blank">{item.url}</a>
</p>
);
});
}
render() {
return (
<div className="detail-view">
<div className="controls">
<button onClick={() => this.handleViewChange('commits')}>提交记录</button>
<button onClick={() => this.handleViewChange('forks')}>分支列表</button>
<button onClick={() => this.handleViewChange('pulls')}>拉取请求</button>
</div>
<div className="data-display">
{this.renderContent()}
</div>
</div>
);
}
}
export default RepositoryDetail;
在数据获取阶段,componentDidMount 生命周期确保了 DOM 挂载后再发起网络请求。状态对象使用了动态键名语法 [dataType] 来更新不同的数据数组。渲染逻辑根据当前的 activeView 状态决定展示哪一类数据,避免了冗余的条件判断。