本文主要介紹React+Webpack+Router搭建React基礎工程的簡單方式。
React 起源於 Facebook 的內部項目,因爲該公司對市場上所有的前端MVC框架都不滿意,就決定自己寫一套,用來構建Instagram。
React主要用於構建UI。你可以在React裏傳遞多種類型的參數,如聲明代碼,幫助你渲染出UI、也可以是靜態的HTML DOM元素、也可以傳遞動態變量、甚至是可交互的應用組件。
特點:
1.聲明式設計:React採用聲明範式,可以輕鬆描述應用。
2.React通過對DOM的模擬,最大限度地減少與DOM的交互。
3.靈活:React可以與已知的庫或框架很好地配合。
如果你想對本文中的React、webpack、router有更加深入的瞭解,請訪問以下網站進行深入的學習:
1.React官方中文文檔:http://reactjs.cn/react/docs/getting-started-zh-CN.html(大多數文章後綴名加入zh-CN會變爲中文)
2.webpack中文指南 :http://webpackdoc.com/loader.html
3.react-router中文文檔:http://www.uprogrammer.cn/react-router-cn/
好了,廢話不多說,下面介紹運用React+Webpack+Router搭建React基礎工程,目錄結構如圖所示:(bundle.js和server.bundle.js爲構建生成的文件)
package.json文件配置:
{
"name": "webpack-react",
"version": "1.0.0",
"description": "",
"main": "./justice/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "if-env NODE_ENV=production && npm run start:prod || npm run start:dev",
"start:dev": "webpack-dev-server --inline --content-base build --history-api-fallback",
"start:prod": "npm run build && node server.bundle.js",
"build:client": "webpack",
"build:server": "webpack --config webpack.server.config.js",
"build": "npm run build:client && npm run build:server",
"dev": "webpack-dev-server --devtool eval --progress --colors --hot --content-base build --history-api-fallback"
},
"keywords": [
"webpack"
],
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.18.2",
"babel-loader": "^6.2.7",
"babel-plugin-react-transform": "^2.0.0",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-0": "^6.16.0",
"body-parser": "^1.4.3",
"css-loader": "^0.21.0",
"express": "^4.4.5",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.9.0",
"jquery": "^3.1",
"less": "^2.7.2",
"less-loader": "^2.2.3",
"react-dom": "^15.3.2",
"react-redux": "^4.4.6",
"react-router": "^3.0.0",
"redux-devtools": "^3.3.1",
"style-loader": "^0.13.0",
"url-loader": "^0.5.7",
"webpack": "^1.13.3",
"webpack-dev-server": "^1.16.2"
},
"dependencies": {
"babel-core": "^6.18.2",
"babel-loader": "^6.2.7",
"babel-plugin-react-transform": "^2.0.0",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-stage-0": "^6.16.0",
"body-parser": "^1.4.3",
"compression": "^1.6.2",
"css-loader": "^0.21.0",
"express": "^4.14.0",
"file-loader": "^0.9.0",
"if-env": "^1.0.0",
"jquery": "^3.1",
"react": "^15.3.2",
"react-dom": "^15.3.2",
"react-redux": "^4.4.6",
"react-router": "^3.0.0",
"redux-devtools": "^3.3.1",
"style-loader": "^0.13.0",
"url-loader": "^0.5.7",
"webpack": "^1.13.3",
"webpack-dev-server": "^1.16.2"
}
}
webpack.config.js構建客戶端
var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var config = {
//構建入口
entry:['webpack/hot/dev-server', path.resolve('', './justice/index.js')],
//構建出口 path:打包文件存放的絕對路徑 filename:打包後的文件名 publicPath:運行時的訪問路徑
output: {
path: path.resolve('', 'build'),
filename: 'bundle.js',
publicPath: '/'
},
module: {
loaders: [
//es6 es7 react加載器
{
test: /\.js?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ["es2015",'stage-0',"react"]
}
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ["es2015",'stage-0',"react"]
}
},
//css解析器
{
test: /\.css$/,
loader: 'style!css'
},
//style解析器
{
test: /\.less$/,
use: [
'style-loader',
{ loader: 'css-loader', options: { importLoaders: 1 } },
'less-loader'
]
},
//圖片處理 小於8K按base64處理
{ test: /\.(png|jpg)$/,
loader: 'url-loader?limit=8192'
}
]
},
babel: {
presets: ['es2015','stage-0','react']
},
resolve:{
//自動擴展文件後綴名,意味着我們require模塊可以省略不寫後綴名
extensions:['','.js','.json']
},
//插件配置 ExtractTextPlugin:提取樣式插件
plugins: process.env.NODE_ENV === 'production' ? [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin("styles.css")
] : [
new webpack.HotModuleReplacementPlugin(),
new ExtractTextPlugin("styles.css")
]
};
module.exports = config;
server.js 服務端配置
var express=require('express');
var path=require('path');
var compression=require('compression');
var react=require('react');
var match=require('react-router');
var RouterContext=require('react-router');
var renderToString=require('react-dom/server');
var app=express();
//must be first! 文件壓縮
app.use(compression());
app.use(express.static(path.join(process.cwd(), 'build')));
app.get('*',function(req,res){
res.sendFile(path.join(process.cwd(),'build','index.html'));
});
function renderPage(appHtml) {
return `
<!doctype html public="storage">
<html>
<meta charset=utf-8/>
<div id=app>${appHtml}</div>
<script src="/bundle.js"></script>
`
}
var PORT = process.env.PORT || 8080
app.listen(PORT, function(){
console.log('Production Express server running at localhost:' + PORT);
});
webpack.server.config.js 構建服務端:
var fs = require('fs')
var path = require('path')
module.exports = {
entry: path.resolve(__dirname, 'server.js'),
output: {
filename: 'server.bundle.js'
},
target: 'node',
// keep node_module paths out of the bundle
externals: fs.readdirSync(path.resolve(__dirname, 'node_modules')).concat([
'react-dom/server', 'react/addons',
]).reduce(function (ext, mod) {
ext[mod] = 'commonjs ' + mod
return ext
}, {}),
node: {
__filename: true,
__dirname: true
},
module: {
loaders: [
{
test: /\.js?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel',
query: {
presets: ['es2015', 'react','stage-0']
}
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ["es2015",'stage-0',"react"]
}
},
{
test: /\.css$/,
loader: 'style!css'
},
{ test: /\.(png|jpg)$/,
loader: 'url-loader?limit=8192'
}
]
}
}
webpack部分到此爲止,接下來我們繼續寫react和router部分:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0,user-scalable=no" />
<meta content="telephone=no" name="format-detection" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="#035c9b">
<!--不構建壓縮css,爲了開發中的調試,直接引用-->
<link rel="stylesheet" href="/css/bootstrap.css" />
<link rel="stylesheet" href="/css/bootstrap-theme.css" />
<link rel="stylesheet" href="/css/theme.css" />
<link rel="stylesheet" href="/css/fixed-data-table.css" />
<link rel="stylesheet" href="/css/rc-tree/index.css" />
<link rel="stylesheet" href="/css/rc-dialog/index.css" />
<link rel="stylesheet" href="/css/rc-calendar/index.css" />
<link rel="stylesheet" href="/css/rc-time-picker/index.css" />
</head>
<body>
<div id="content">
</div>
</body>
<!--iframe模式 因爲在package.json中配置了inline模式,此行註釋 -->
<!--<script src="http://localhost:8080/webpack-dev-server.js"></script>-->
<!--如果使用browserHistory 這裏請用/bundle.js -->
<!-- 其他也要用 /index.css 這種形式 -->
<script src="/bundle.js"></script>
</html>
從上面的webpack.config.js中可知,我們構建的入口爲index.js,index.js也必爲路由的入口,配置如下:
index.js
var React=require('react');
var ReactDOM=require('react-dom');
var Router=require('react-router').Router;
//hashHistory url中帶有#號,browserHistory不帶有#號
var hashHistory=require('react-router').hashHistory;
var browserHistory=require('react-router').browserHistory;
var routes=require('./routes.js').routes
//url上的userName 和 repoName 會傳到this.props.params中
//IndexRoute 當沒有其他明確url時會首先render
ReactDOM.render((
<Router routes={routes} history={browserHistory}></Router>
), document.getElementById('content'))
我們發現index.js引用了routes即路由的集合,這個集合存在於routes.js
routes.js的內容如下:
var React=require('react');
var Route= require('react-router').Route;
var IndexRoute=require('react-router').IndexRoute;
var Home=require('./home').Home;
var Welcome=require('./page/welcome').Welcome;
var Tree=require('./page/tree.jsx').Tree;
var Silder=require('./page/silder.jsx').Silder;
var Calendar=require('./page/calendar.jsx').Calendar;
var Dialog=require('./page/dialog.jsx').Dialog;
var Table=require('./page/table.jsx').Table;
var Datepicker=require('./page/datepicker').Datepicker;
var $=require('jquery');
//即進入路由時觸發的函數
var enterFun=function(nextState,replace){
if(nextState.location.pathname=='/'){
$('#home').addClass('active');
}
if(nextState.location.pathname!='/'){
$('#home').removeClass('active');
}
}
//component代表使用的組件 IndexRoute爲首頁路由 path爲路徑 路由按層級進入,一開始進入Home組件 然後組合首頁路由同時進入Welcome組件
var routes=(
<Route onEnter={enterFun} path="/" component={Home} >
<IndexRoute onEnter={enterFun} component={Welcome}></IndexRoute>
<Route onEnter={enterFun} path="/tree" component={Tree}></Route>
<Route onEnter={enterFun} path="/table" component={Table}></Route>
<Route onEnter={enterFun} path="/calendar" component={Calendar}></Route>
<Route onEnter={enterFun} path="/dialog" component={Dialog}></Route>
<Route onEnter={enterFun} path="/silder" component={Silder}></Route>
<Route onEnter={enterFun} path="/datepicker" component={Datepicker}></Route>
</Route>
);
exports.routes=routes;
首頁路由welcome.js的內容如下
var React=require('react');
var Link= require('react-router').Link;
var IndexLink=require('react-router').IndexLink;
var Welcome = React.createClass({
render : function(){
return (
<div>
歡迎來到React<br/>
構建方式爲React+webpack+router
</div>
);
}
})
exports.Welcome=Welcome;
Home.js的內容如下
var React=require('react');
var Link= require('react-router').Link;
var IndexLink=require('react-router').IndexLink;
var Header=require('./header.js').Header;
var Footer=require('./footer.js').Footer;
var Navigation=require('./nav.js').Navigation;
var Menu=require('./menu.js').Menu;
var Welcome=require('./page/welcome.js').Welcome;
var Home = React.createClass({
render : function(){
return (
<div className="pagewrap">
<Header name='React門戶'/>
<Navigation />
<div className="layout-l-r">
<Menu/>
<section className="main">
<ol className="breadcrumb">
<li><a href="#">主頁</a></li>
<li><a href="#">數據分析</a></li>
<li className="active">表格</li>
</ol>
<div className="main-view col-xs-12">
{this.props.children||<Welcome/>}
</div>
</section>
</div>
<Footer />
</div>
);
}
})
exports.Home=Home;
接下來是menu.js 爲訪問路由的方式: to='xxx'中的路徑與route中的path對應
var React=require('react');
var Link= require('react-router').Link;
var IndexLink=require('react-router').IndexLink;
//<Link to='silder' activeClassName="active" className="list-group-item">React SilderBar</Link>
var Menu = React.createClass({
render : function(){
return (
<section className="leftside">
<div className="list-group navfun">
<Link to='/' id='home' className="list-group-item">Home</Link>
<Link to='tree' activeClassName="active" className="list-group-item">React Tree</Link>
<Link to='table' activeClassName="active" className="list-group-item">React Table</Link>
<Link to='calendar' activeClassName="active" className="list-group-item">React Calendar</Link>
<Link to='dialog' activeClassName="active" className="list-group-item">React Dialog</Link>
</div>
</section>
);
}
})
exports.Menu=Menu;
header.js(footer.js結構與之形式相似,這裏不贅述)
var React=require('react');
var Header = React.createClass({
render : function(){
return (
<header>
<h1>{this.props.name}</h1>
</header>
);
}
})
exports.Header=Header;
table.jsx(其他組件與之結構相似,這裏不贅述)
var React=require('react');
var PropTypes=require('react').PropTypes;
var Link= require('react-router').Link;
var IndexLink=require('react-router').IndexLink;
var myTree = React.createClass({
render() {
return (<div style={{ margin: '0 20px' }}>
tree
</div>);
}
});
exports.Tree=myTree;
至此,一個簡單的react+webpack+router的基礎工程搭建完畢
第一步:執行npm install 安裝node modules
第二步:執行npm start 或npm run start:dev 運行dev模式
或
執行npm run start:prod 運行生產模式