前言
使用react-router、webpack、redux一步步启动一个react app项目
项目github源码:https://github.com/chenshaomei/react-douban
初始化项目
1、首先创建一个空文件夹 react-douban,初始化package.json
$ npm init
2、package.json文件添加依赖
{
"name": "react-douban",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "browser-sync start --server --files **/*.css, **/*.html, **/*.js",
"dev": "webpack-dev-server"
},
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.25.0",
"babel-loader": "^7.1.1",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"browser-sync": "^2.18.12",
"css-loader": "^0.28.4",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-hot-loader": "^1.3.1",
"react-router": "^4.1.1",
"style-loader": "^0.18.2"
},
"dependencies": {
"file-loader": "^0.11.2",
"html-loader": "^0.4.5",
"html-webpack-plugin": "^2.29.0",
"isomorphic-fetch": "^2.2.1",
"query-string": "^4.3.4",
"react-redux": "^5.0.5",
"react-router": "^2.8.1",
"redux": "^3.7.2",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.2.0",
"url-loader": "^0.5.9",
"webpack": "^3.2.0",
"webpack-dev-server": "^2.5.1",
"whatwg-fetch": "^2.0.3"
}
}
3、运行命令下载依赖模块:
$ npm install
4、在react-douban
工程内,创建以下目录:
5、在 index.html
文件中添加以下代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no,minimal-ui">
<title>react-project</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
6、在 .babelrc
文件中添加以下代码
{
presets: [['es2015'], ['react']]
}
配置webpack
1、全局安装
$ npm install webpack -g
2、配置webpack文件,webpack.config.js
var path = require('path');
var webpack = require('webpack');
var HtmlwebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 打包的入口文件
entry: __dirname + '/app/main.js',
// 打包输出文件夹以及文件名
output: {
path: __dirname+'/build' ,
publicPath: '/',
filename: 'bundle.js'
},
//启动的webpack server配置
devServer: {
contentBase: path.join(__dirname, "build"), //以build为根目录提供文件
historyApiFallback: true,
hot: true,
port:8092, //端口号
inline: true,
// api 代理转发
proxy:{
"/api": {
target: "http://api.douban.com/v2/",
pathRewrite: {"^/api" : ""},
changeOrigin: true
}
}
},
devtool: 'source-map',
//引入模块时就不需要写后缀了,会自动补全
resolve: {
modules: [
path.join(__dirname, "app"),
"node_modules"
],
extensions: ['.js', '.json', '.css', '.scss']
},
// 加载器,对模块的处理逻辑
module: {
loaders: [
{test:/\.css$/, loader: 'style-loader!css-loader'},
{
test: /\.html?$/,
loader: 'html-loader',
},
{test:/\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader?limit=8192'},
{test:/\.js$/, loader: 'react-hot-loader!babel-loader', exclude: /node_modules/},
]
},
//插件
plugins: [
// 自动生成 html
new HtmlwebpackPlugin({
template: __dirname + "/index.html" // 指向自己创建的index.html的位置
}),
new webpack.HotModuleReplacementPlugin()
]
};
react-router
1、在main.js
文件中添加以下内容(入口文件)
iimport React from 'react';
import { render } from 'react-dom';
import { Router, browserHistory } from 'react-router';
import { Provider } from 'react-redux';
import configureStore from './stores';
// 引入store
const store = configureStore();
// 引入路由
import { routes } from './router';
// 引入全局css
import './css/base.css';
render(
<Provider store = { store }>
<Router history={browserHistory}>
{
routes
}
</Router>
</Provider>
, document.getElementById('app'));
2、在router.js
文件中添加以下内容(路由文件)
import React from 'react';
import { Router, Route, browserHistory, IndexRoute, Redirect } from 'react-router';
// 引入pages组件
import App from './App';
import Index from './pages/Index';
import Home from './pages/Home';
import Login from './pages/Login';
import Details from './pages/Details';
// 定义路由
const routes = (
<Route path="/" component={ App }>
<IndexRoute component={ Index }/>
<Route path="/" component={ Index }>
<Route path="/home" component={ Home } />
<Route path="/my" component={ Login } />
</Route>
<Route path="/details/:id" component={ Details } />
</Route>
)
export { routes };
3、在App.js
文件中添加以下内容(根组件)
import React from 'react';
import ReactDom from 'react-dom';
import Nav from './components/Nav/Nav';
export default React.createClass({
render() {
return <div>
{this.props.children}
</div>
}
})
4、在pages/Index.js
中添加以下内容
在Index组件下,渲染Home
(首页) 和My
(我的)页面 ,并且渲染两个页面有公共的导航组件(components/Nav/Nav
)
import React from 'react';
import ReactDom from 'react-dom';
import Nav from '../components/Nav/Nav';
import Home from './Home';
export default class Index extends React.Component{
constructor(props){
super(props);
// nav data
this.state = {
navList: [
{
path:'/home',
icon:require('../images/icons/home.png'),
txt:'首页'
},
{
path:'/my',
icon:require('../images/icons/my.png'),
txt:'我的'
}
]
}
}
render(){
return <div>
{this.props.children || <Home />}
<Nav navList = { this.state.navList }/>
</div>
}
}
5、创建导航组件components/Nav/Nav.js
- 在
components/Nav/Nav.js
中添加以下内容
activeIndex 是导航选中的下标,设置显示高亮
import React from 'react';
import { Link } from 'react-router';
import './Nav.css';
export default class Nav extends React.Component{
constructor(props){
super(props);
}
render(){
let activeIndex = 0;
if(location.pathname !='/'){
activeIndex = -1;
}
// nav data
let navList = this.props.navList || [];
return <div className="nav">
<ul className="nav-ul">
{
navList.map((item,index)=>{
return <li key={index} ><Link to={item.path} className={index==activeIndex?'active':''} activeClassName="active"><i></i><span className="txt">{item.txt}</span></Link></li>
})
}
</ul>
</div>
}
}
- 在
components/Nav/Nav.css
中添加以下内容
.nav{
position: fixed;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 49px;
}
.nav::before{
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
width: 100%;
height: 1px;
background: #ccc;
-webkit-transform-origin:0 0;
transform-origin:0 0;
-webkit-transform:scaleY(0.5);
transform:scaleY(0.5);
}
.nav-ul{
display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
display: -webkit-flex; /* NEW - Chrome */
display: flex;
width: 100%;
height: 100%;
background: #fff;
-webkit-box-pack: center;
-webkit-box-align: center;
justify-content: center;
align-items: center
}
.nav-ul li{
width: 0%;
-webkit-box-flex: 1;
-webkit-flex: 1;
flex: 1;
}
.nav-ul li a{
display: block;
text-align: center;
}
.nav-ul li a span{
display: block;
color: #999;
font-size: 12px;
}
.nav-ul li a i{
display: block;
width: 24px;
height: 24px;
margin: 0 auto;
}
.nav-ul li:nth-child(1) a i{
background: url(../../images/icons/home.png) no-repeat top center;
background-size: 100%;
}
.nav-ul li:nth-child(2) a i{
background: url(../../images/icons/search.png) no-repeat top center;
background-size: 100%;
}
.nav-ul li:nth-child(3) a i{
background: url(../../images/icons/my.png) no-repeat top center;
background-size: 100%;
}
.nav-ul li:nth-child(1) a.active i{
background: url(../../images/icons/home-active.png) no-repeat top center;
background-size: 100%;
}
.nav-ul li:nth-child(2) a.active i{
background: url(../../images/icons/search-active.png) no-repeat top center;
background-size: 100%;
}
.nav-ul li:nth-child(3) a.active i{
background: url(../../images/icons/my-active.png) no-repeat top center;
background-size: 100%;
}
.nav-ul li a.active span{
color: #333;
}
redux
1、action
action:描述“发生了什么”
它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store
2、reducer
reducer:根据 action 更新 state
- 创建reducer
在reducers/index.js
文件中添加如下内容
import { combineReducers } from 'redux';
import home from './home'
//使用redux的combineReducers方法将所有reducer打包合并起来
const rootReducer = combineReducers({
home
})
export default rootReducer
拆分 Reducer,一个reducer只负责一个页面的state;(例如home只负责管理首页的state更新),然后再使用redux
的combineReducers
方法将所有reducer
打包合并起来
3、store
Store 就是把action
和reducer
联系到一起的对象,Redux 应用只有一个单一的 store
- 注册store
在stores/index.js
文件中添加如下内容
// 注册store
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { createLogger } from 'redux-logger' // 利用redux-logger打印日志
import reducer from '../reducers'
// 调用日志打印方法
const loggerMiddleware = createLogger()
//applyMiddleware来自redux可以包装 store 的 dispatch
//thunk作用是使action创建函数可以返回一个function代替一个action对象
const createStoreWithMiddleware = applyMiddleware(
thunk,
loggerMiddleware
)(createStore)
export default function configureStore(initialState) {
const store = createStoreWithMiddleware(reducer, initialState)
//热替换选项
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextReducer = require('../reducers')
store.replaceReducer(nextReducer)
})
}
return store
}