前言
使用react-router、webpack、redux一步步啟動(dòng)一個(gè)react app項(xiàng)目
項(xiàng)目github源碼:https://github.com/chenshaomei/react-douban
初始化項(xiàng)目
1、首先創(chuàng)建一個(gè)空文件夾 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、運(yùn)行命令下載依賴模塊:
$ npm install
4、在react-douban工程內(nèi),創(chuàng)建以下目錄:

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'
},
//啟動(dòng)的webpack server配置
devServer: {
contentBase: path.join(__dirname, "build"), //以build為根目錄提供文件
historyApiFallback: true,
hot: true,
port:8092, //端口號(hào)
inline: true,
// api 代理轉(zhuǎn)發(fā)
proxy:{
"/api": {
target: "http://api.douban.com/v2/",
pathRewrite: {"^/api" : ""},
changeOrigin: true
}
}
},
devtool: 'source-map',
//引入模塊時(shí)就不需要寫后綴了,會(huì)自動(dòng)補(bǔ)全
resolve: {
modules: [
path.join(__dirname, "app"),
"node_modules"
],
extensions: ['.js', '.json', '.css', '.scss']
},
// 加載器,對(duì)模塊的處理邏輯
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: [
// 自動(dòng)生成 html
new HtmlwebpackPlugin({
template: __dirname + "/index.html" // 指向自己創(chuàng)建的index.html的位置
}),
new webpack.HotModuleReplacementPlugin()
]
};
react-router
1、在main.js文件中添加以下內(nèi)容(入口文件)
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文件中添加以下內(nèi)容(路由文件)
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文件中添加以下內(nèi)容(根組件)
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中添加以下內(nèi)容
在Index組件下,渲染Home(首頁) 和My(我的)頁面 ,并且渲染兩個(gè)頁面有公共的導(dǎo)航組件(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、創(chuàng)建導(dǎo)航組件components/Nav/Nav.js
- 在
components/Nav/Nav.js中添加以下內(nèi)容
activeIndex 是導(dǎo)航選中的下標(biāo),設(shè)置顯示高亮
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中添加以下內(nèi)容
.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:描述“發(fā)生了什么”
它是 store 數(shù)據(jù)的唯一來源。一般來說你會(huì)通過 store.dispatch() 將 action 傳到 store
2、reducer
reducer:根據(jù) action 更新 state
- 創(chuàng)建reducer
在reducers/index.js文件中添加如下內(nèi)容
import { combineReducers } from 'redux';
import home from './home'
//使用redux的combineReducers方法將所有reducer打包合并起來
const rootReducer = combineReducers({
home
})
export default rootReducer
拆分 Reducer,一個(gè)reducer只負(fù)責(zé)一個(gè)頁面的state;(例如home只負(fù)責(zé)管理首頁的state更新),然后再使用redux的combineReducers方法將所有reducer打包合并起來
3、store
Store 就是把action和reducer聯(lián)系到一起的對(duì)象,Redux 應(yīng)用只有一個(gè)單一的 store
- 注冊(cè)store
在stores/index.js文件中添加如下內(nèi)容
// 注冊(cè)store
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { createLogger } from 'redux-logger' // 利用redux-logger打印日志
import reducer from '../reducers'
// 調(diào)用日志打印方法
const loggerMiddleware = createLogger()
//applyMiddleware來自redux可以包裝 store 的 dispatch
//thunk作用是使action創(chuàng)建函數(shù)可以返回一個(gè)function代替一個(gè)action對(duì)象
const createStoreWithMiddleware = applyMiddleware(
thunk,
loggerMiddleware
)(createStore)
export default function configureStore(initialState) {
const store = createStoreWithMiddleware(reducer, initialState)
//熱替換選項(xiàng)
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextReducer = require('../reducers')
store.replaceReducer(nextReducer)
})
}
return store
}