React + Node 单页应用「一」前端搭建
记录最近做的一个 demo,前端使用 React
,用 React Router
实现前端路由,Koa 2
搭建 API Server, 最后通过 Nginx
做请求转发。
这是第一篇,主要介绍下前端代码的构建、React router
使用中遇到问题,以及前端开发完成后部署相关工作。
功能介绍
GitCard 可以通过 GitHub 授权获取用户基本信息
- 在首页模块,可以查看最近登录的用户,并点击头像查看该用户的详细信息
- 在
/Comment
模块中可以发表评论,并删除自己的评论 - 在
/Detail
模块中可以查看用户在 Github 上的基本信息(代码库,Follower、Following 以及更多开发的信息),你可以在这个基础上做更多有意思的事情,支持 Follow 和 Unfollow 操作,当然,你可以加上 Star 和 Unstar 操作,异曲同工。🈳️
依赖版本
- 构建工具: create-react-app
- react: 16.0.0
- react-router: 4.2.2
- 网络请求: axios
- UI: material-ui
项目搭建
前端构建工具使用 React 官方的 create-react-app,快速生成可执行的项目结构。下面是快速上手流程,详细的内容可以参见 官方文档。
// 全局安装 create-react-app
npm install -g create-react-app
//生成项目,项目名 my-app(自定义)
create-react-app my-app
cd my-app
// 安装依赖
npm install
// 开发环境
npm start
// 打包
npm run build
生成项目后,项目文件结构如下,npm run build
执行后,目录下会出现 /build/
目录,存放构建后的文件。
/**
* my-app 目录结构
*/
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ └── favicon.ico
│ └── index.html
│ └── manifest.json
└── src
└── App.css
└── App.js //项目入口
└── App.test.js
└── index.css
└── index.js
└── logo.svg
└── registerServiceWorker.js
我们在 /src/
目录下新建 /components/
目录,用于存放本次项目中的所有自定义组件,最终的目录如下
.
├── README.md
├── build
├── package.json
├── public
├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── components
│ │ ├── Comments
│ │ │ ├── comment.css
│ │ │ └── index.jsx
│ │ ├── Events
│ │ │ └── index.jsx
│ │ ├── HomePage
│ │ │ └── index.jsx
│ │ ├── UserDetail
│ │ │ └── index.jsx
│ │ └── layouts
│ │ ├── Header.jsx
│ │ └── SideMenu.jsx
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── registerServiceWorker.js
│ └── utils
│ ├── api.jsx
│ ├── fetch.jsx
│ └── keygen.js
└── yarn.lock
前端路由
这次做的项目是单页应用,通过 React router
实现前端路由,Container
通过路由选择显示的模块内容,在 Header
显示路由切换入口,页面结构图如下。
在 /src/App.js
布局并设置路由,将 Header
和 Container
包在 Router
下,在 Header
的 Menu 组件中设置路由链接,并在 Container
中设置路由指向对应组件。
Menu 中 Detail 链接在用户授权登录后就指向登录用户信息,否则跳转到授权登录页面。
// 引入 React router
import {
BrowserRouter as Router,
Link,
Route
} from "react-router-dom"
// 在 layout-content 中设置路由对应的组件
<Router>
<div className="App">
<Header loginInfo={this.state.loginInfo}></Header>
<div className="layout-container">
<div className="layout-content">
<Route exact path="/" component={ HomePage } ></Route>
<Route path="/user/:userid" component={ UserDetail } ></Route>
<Route path="/events" component={Events}></Route>
<Route path="/comments" render={ ()=><Comments loginInfo={this.state.loginInfo} /> }></Route>
</div>
</div>
</div>
</Router>
// 在 Header 中设置 Link
<Menu>
<Link to="/">
<MenuItem primaryText="Home" />
</Link>
<Link to="/comments">
<MenuItem primaryText="Comments"/>
</Link>
{
this.props.loginInfo.login ?
(<Link to={ '/user/' + this.props.loginInfo.login }>
<MenuItem primaryText="Detail" />
</Link>)
: (<MenuItem primaryText="Login" onClick={this.login} />)
}
</Menu>
数据操作
路由下发数据
上面的代码的路由中,我们通过两种方式下发数据到组件中,通过路由参数将用户 ID 下发到 Detail
组件,以及通过 props
讲用户基本信息下发到 Comment
组件中。
// 通过路由参数下发的 Link 以及对应的 Route
<Link to={ '/user/' + this.props.loginInfo.login }><MenuItem primaryText="Detail" /></Link>
<Route path="/user/:userid" component={ UserDetail } ></Route>
// 通过 props 下发
<Route path="/comments" render={ ()=><Comments loginInfo={this.state.loginInfo} /> }></Route>
通过路由参数获取下发数据,如果在 /user/lijundong
跳转到 /user/free-free
会出现路由改变,数据不刷新的情况,这里需要用 componentWillReceiveProps(nextProps)
处理。
在 Detail
组件中获取 ID,
const userId = this.props.match.params.userid;
以及在 Comment
中获取用户信息
const loginInfo = this.props.loginInfo;
数据请求
我们在这里选择用 axios 网络请求,为了方便管理,将所有的 API 统一放在 /src/utils/api.jsx
中,利于后期维护。
没有使用 Fetch 是因为 Github API 只支持 XHR 跨域请求,Fetch 跨域请求会导致请求 request type 变成 option
。
数据更新
setState
因为这次的数据并不复杂,所以没有引入 MobX 或者 Redux 处理,数据更新全程使用 setState
,不过由于 setState
是异步操作,所以,某些情况下,需要用到回调函数,如下
setState(
{ name: "Michael" },
() => console.log(this.state.name)
);
// Michael
componentWillReceiveProps
上面提到过,通过路由参数获取下发数据,如果在 /user/lijundong
跳转到 /user/free-free
会出现路由改变,数据不刷新的情况,这是因为 componentDidMount()
只会执行一次,props
更新不会触发重新获取数据,这里可通过 componentWillReceiveProps()
解决。
componentWillReceiveProps()
会在每次 props
更新时触发,通过新参数重新获取数据列表。
componentWillReceiveProps(nextProps){
const userId = nextProps.match.params.userid;
// 通过新参数获取数据
Axios.get(url)
}
项目构建部署
项目开发结束,通过 npm run build
进行打包,打包完成后,生成的 /build/
即我们最终上线的版本,因为是前后端分离的项目,前端需要自起 Server,可以通过这两种方案进行部署
- 使用 create-react-app 自带的
serve -s build
命令起 Server(我当前使用的方式) - 或者通过第三方工具
http-server
、anywhere
等起 Server
服务开启后,可以正常访问就代表前端部分已经完成,暴露的端口供最后 Nginx 配置使用。