本身在开发React实战中总结出的构建和相关各项配置与相关功能用法的总结,很是适合刚入门的用户。javascript
entry point(bundle)
,chunk
,module
因为构建相关例如webpack,babel等更新的较快,因此本教程如下面各类模块的版本号为主,切勿轻易修改或更新版本。css
"dependencies": {
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-loader": "^7.1.4",
"babel-plugin-transform-async-to-generator": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"babel-preset-stage-3": "^6.24.1",
"css-loader": "^0.28.11",
"eslint": "^4.19.1",
"eslint-loader": "^2.0.0",
"eslint-plugin-react": "^7.9.1",
"file-loader": "^1.1.11",
"history": "^4.7.2",
"html-webpack-plugin": "^3.2.0",
"react": "^16.4.0",
"react-dom": "^16.4.0",
"react-hot-loader": "^4.0.0",
"react-redux": "^5.0.7",
"react-router-dom": "^4.3.1",
"react-router-redux": "^5.0.0-alpha.9",
"redux": "^4.0.0",
"sass-loader": "^7.0.3",
"style-loader": "^0.21.0",
"url-loader": "^1.0.1",
"webpack": "^4.12.0",
"webpack-cli": "^3.0.3",
"webpack-dev-server": "^3.1.1"
}
复制代码
开发和发布版本的配置文件是分开的,多入口页面的目录结构。html
react-family/
|
|──dist/ * 发布版本构建输出路径
|
|──dev/ * 调试版本构建输出路径
|
|──src/ * 工具函数
| |
| |—— component/ * 各页面公用组件
| |
| |—— page/ * 页面代码
| | |—— index/ * 页面代码
| | | |—— Main/ * 组件代码
| | | | |—— Main.jsx * 组件jsx
| | | | |—— Main.scss * 组件css
| | |
| | |—— detail/ * 页面代码
| |
| |—— static/ * 静态文件js,css
|
|
|──webpack.config.build.js * 发布版本使用的webpack配置文件
|──webpack.config.dev.js * 调试版本使用的webpack配置文件
|──.eslint * eslint配置文件
|__.babelrc * babel配置文件
复制代码
mkdir react-family-bucket
复制代码
cd react-family-bucket
npm init
复制代码
若是有特殊须要,能够填入本身的配置,一路回车下来,会生成一个package.json
,里面是你项目的基本信息,后面的npm依赖安装也会配置在这里。vue
npm install webpack --save
or
npm install webpack --g
复制代码
--save
是将当前webpack安装到react-family-bucket下的/node_modules
。
--g
是将当前webpack安装到全局下面,能够在node的安装目录下找到全局的/node_modules
。html5
touch webpack.config.dev.js
复制代码
新建一个app.jsjava
touch app.js
复制代码
写入基本的webpack配置,能够参考这里:node
const path = require('path');
const srcRoot = './src';
module.exports = {
// 输入配置
entry: [
'./app.js'
],,
// 输出配置
output: {
path: path.resolve(__dirname, './dev'),
filename: 'bundle.min.js'
},
};
复制代码
3, 执行webpack命令 若是是全局安装:react
webpack --config webpack.config.dev.js
复制代码
若是是当前目录安装:webpack
./node_modules/.bin/webpack --config webpack.config.dev.js
复制代码
在package.json中添加执行命令:ios
"scripts": {
"dev": "./node_modules/.bin/webpack --config webpack.config.dev.js",
},
复制代码
执行npm run dev
命令以后,会发现须要安装webpack-cli
,(webpack4以后须要安装这个)
npm install webpack-cli --save
复制代码
去除WARNING in configuration
警告,在webpack.config.dev.js增长一个配置便可:
...
mode: 'development'
...
复制代码
成功以后会在dev下面生成bundle.min.js表明正常。
若是想要动态监听文件变化须要在命令后面添加 --watch
npm install react react-dom --save
复制代码
mkdir src
mkdir page
cd page
复制代码
建立index
mkdir index
cd index & touch index.js & touch index.html
复制代码
index.js
import ReactDom from 'react-dom';
import Main from './Main/Main.jsx';
ReactDom.render(<Main />, document.getElementById('root')); 复制代码
index.html
<!DOCTYPE html>
<html>
<head>
<title>index</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
</head>
<body>
<div id="root"></div>
</body>
</html>
复制代码
import React from 'react';
class Main extends React.Component {
constructor(props) {
super(props);
}
render() {
return (<div>Main</div>);
}
}
export default Main;
复制代码
export
和 export default
区别:export能够有多个
xx.js:
export const test1 = 'a'
export function test2() {}
yy.js:
import { test1, test2 } from 'xx.js';
复制代码
export default只能有1个
xx.js:
let test1 = 'a';
export default test1;
yy.js:
import test1 from 'xx.js';
复制代码
export
和 module.exports
let exports = module.exports;
复制代码
entry: [
path.resolve(srcRoot,'./page/index/index.js')
],
复制代码
npm install css-loader sass-loader style-loader file-loader --save
复制代码
配置:
module: {
// 加载器配置
rules: [
{ test: /\.css$/, use: ['style-loader', 'css-loader'], include: path.resolve(srcRoot)},
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'], include: path.resolve(srcRoot)}
]
},
复制代码
npm install url-loader --save
复制代码
配置:
module: {
// 加载器配置
rules: [
{ test: /\.(png|jpg|jpeg)$/, use: 'url-loader?limit=8192&name=images/[name].[hash].[ext]', include: path.resolve(srcRoot)}
]
},
复制代码
limit:
表示超过多少就使用base64来代替,单位是byte
name:
能够设置图片的路径,名称和是否使用hash 具体参考这里
bebel是用来解析es6语法或者是es7语法分解析器,让开发者可以使用新的es语法,同时支持jsx,vue等多种框架。
npm install babel-core babel-loader --save
复制代码
配置:
module: {
// 加载器配置
rules: [
{ test: /\.(js|jsx)$/, use: [{loader:'babel-loader'}] ,include: path.resolve(srcRoot)},
]
},
复制代码
babel配置文件:.babelrc
touch .babelrc
复制代码
配置:
{
"presets": [
"es2015",
"react",
"stage-0"
],
"plugins": []
}
复制代码
babel支持自定义的预设(presets)或插件(plugins),只有配置了这两个才能让babel生效,单独的安装babel是无心义的
presets
:表明babel支持那种语法(就是你用那种语法写),优先级是从下往上,state-0|1|2|..
表明有不少没有列入标准的语法回已state-x表示,参考这里
plugins
:表明babel解析的时候使用哪些插件,做用和presets相似,优先级是从上往下。 依次安装:
npm install babel-preset-es2015 babel-preset-react babel-preset-stage-0 --save
复制代码
npm install --save babel-polyfill
复制代码
使用:
import "babel-polyfill";
复制代码
babel-polyfill
时有一些问题:这时就须要transform-runtime
来帮咱们有选择性的引入
npm install --save babel-plugin-transform-runtime
复制代码
配置文件:
{
"plugins": [
["transform-runtime", {
"helpers": false,
"polyfill": false,
"regenerator": true,
"moduleName": "babel-runtime"
}]
]
}
复制代码
记得咱们以前新建的index.html么 咱们执行构建命令以后并无将index.html打包到dev目录下 咱们须要HtmlWebpackPlugin来将咱们output的js和html结合起来
npm install html-webpack-plugin --save
复制代码
配置:
const HtmlWebpackPlugin = require('html-webpack-plugin');
...
plugins: [
new HtmlWebpackPlugin({
filename: path.resolve(devPath, 'index.html'),
template: path.resolve(srcRoot, './page/index/index.html'),
})
]
复制代码
filename
:能够设置html输出的路径和文件名
template
:能够设置已哪一个html文件为模版 更多参数配置能够参考这里
npm install redux react-redux --save
复制代码
reducers
,actions
目录和文件|—— index/
|—— Main/ * 组件代码
| |—— Main.jsx * 组件jsx
| |—— Main.scss * 组件css
|
|—— actions/
| |—— actionTypes.js * action常量
| |—— todoAction.js * action
|
|—— reducers/
| |—— todoReducer.js * reducer
|
|—— store.js
|
|—— index.js
复制代码
index.js
import ReactDom from 'react-dom';
import React from 'react';
import Main from './Main/Main.jsx';
import store from './store.js';
import { Provider } from 'react-redux';
ReactDom.render(
<Provider store={store}> <Main /> </Provider>
, document.getElementById('root'));
复制代码
store.js
import { createStore } from 'redux';
import todoReducer from './reducers/todoReducer.js';
const store = createStore(todoReducer);
export default store;
复制代码
tabReducer.js
import { ADD_TODO } from '../actions/actionTypes.js';
const initialState = {
todoList: []
};
const addTodo = (state, action) => {
return { ...state, todoList: state.todoList.concat(action.obj) }
}
const todoReducer = (state = initialState, action) => {
switch(action.type) {
case ADD_TODO: return addTodo(state, action);
default: return state;
}
};
export default todoReducer;
复制代码
Main.jsx
import React from 'react';
import { connect } from 'react-redux';
import { addTodo } from '../actions/todoAction.js';
class Main extends React.Component {
onClick(){
let text = this.refs.input;
this.props.dispatch(addTodo({
text: text.value
}))
}
render() {
return (
<div> <input ref="input" type="text"></input> <button onClick={()=>this.onClick()}>提交</button> <ul> {this.props.todoList.map((item, index)=>{ return <li key={index}>{item.text}</li> })} </ul> </div>
);
}
}
export default connect(
state => ({
todoList: state.todoList
})
)(Main);
复制代码
todoAction.js
import { ADD_TODO } from './actionTypes.js';
export const addTodo = (obj) => {
return {
type: ADD_TODO,
obj: obj
};
};
复制代码
webpack-dev-server是一个小型的Node.js Express
服务器,它使用webpack-dev-middleware来服务于webpack的包。
npm install webpack-dev-server --save
复制代码
修改在package.json中添加的执行命令:
"scripts": {
"dev": "./node_modules/.bin/webpack-dev-server --config webpack.config.dev.js",
},
复制代码
devServer: {
"contentBase": devPath,
"compress": true,
},
复制代码
contentBase
表示server文件的根目录 compress
表示开启gzip 更多的配置文档参考这里
webpack-dev-server
默认状况下会将output的内容放在内存中,是看不到物理的文件的,若是想要看到物理的dev下面的文件能够安装write-file-webpack-plugin这个插件。
webpack-dev-server
默认会开启livereload功能
devtool
功能:devtool: 'inline-source-map'
以后,利用source-map你在chrome控制台看到的source源码都是真正的源码,未压缩,未编译前的代码,没有添加,你看到的代码是真实的压缩过,编译过的代码,更多devtool的配置能够参考这里在以前的配置中,都是基于单入口页面配置的,entry和output只有一个文件,可是实际项目不少状况下是多页面的,在配置多页面时,有2中方法能够选择:
[name]
关键字来区分输出文件例如:entry: {
index: [path.resolve(srcRoot,'./page/index/index1.js'),path.resolve(srcRoot,'./page/index/index2.js')],
detail: path.resolve(srcRoot,'./page/detail/detail.js'),
home: path.resolve(srcRoot,'./page/home/home.js'),
},
output: {
path: path.resolve(__dirname, './dev'),
filename: '[name].min.js'
},
复制代码
const pageDir = path.resolve(srcRoot, 'page');
function getEntry() {
let entryMap = {};
fs.readdirSync(pageDir).forEach((pathname)=>{
let fullPathName = path.resolve(pageDir, pathname);
let stat = fs.statSync(fullPathName);
let fileName = path.resolve(fullPathName, 'index.js');
if (stat.isDirectory() && fs.existsSync(fileName)) {
entryMap[pathname] = fileName;
}
});
return entryMap;
}
{
...
entry: getEntry()
...
}
复制代码
本demo采用的是第二中写法,可以更加灵活。
entry point(bundle)
,chunk
,module
在webpack中,如何理解entry point(bundle)
,chunk
,module
?
根据图上的表述,我这里简单说一下便于理解的结论:
entry point
.chunk
。module
,这点很好理解。chunk
的分类比较特别,有entry chunk
,initial chunk
,normal chunk
,参考这个文章chunk
对应一个output,在使用了CommonsChunkPlugin
或者require.ensure
以后,chunk
就变成了initial chunk
,normal chunk
,这时,一个chunk
对应多个output。以前咱们配置HtmlWebpackPlugin
时,一样采用的是但页面的配置,这里咱们将进行多页面改造,entryMap
是上一步获得的entry:
function htmlAarray(entryMap) {
let htmlAarray = [];
Object.keys(entryMap).forEach(function(key){
let fullPathName = path.resolve(pageDir, key);
let fileName = path.resolve(fullPathName, key + '.html')
if (fs.existsSync(fileName)) {
htmlAarray.push(new HtmlWebpackPlugin({
chunks: key, // 注意这里的key就是chunk
filename: key + '.html',
template: fileName,
inlineSource: '.(js|css)'
}))
}
});
return htmlAarray;
}
复制代码
修改plugin配置:
plugins: [
...
].concat(htmlMap)
复制代码
模块热替换(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它容许在运行时更新各类模块,而无需进行彻底刷新,很高大上有木有!
下面说一下配置方法,它须要结合devServer
使用:
devServer: {
hot: true // 开启HMR
},
复制代码
开启plugin:
const webpack = require('webpack');
plugins: [
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
].concat(htmlMap)
复制代码
结合React一块儿使用:
npm install react-hot-loader --save
复制代码
并新建一个Container.jsx:
import React from 'react';
import Main from './Main.jsx';
import { hot } from 'react-hot-loader'
class Container extends React.Component {
render() {
return <Main /> } } export default hot(module)(Container); 复制代码
结合redux:若是项目没有使用redux,能够无需配置后面2步
2. 修改store.js新增下面代码,为了让reducer也能实时热替换
if (module.hot) {
module.hot.accept('./reducers/todoReducer.js', () => {
const nextRootReducer = require('./reducers/todoReducer.js').default;
store.replaceReducer(nextRootReducer);
});
}
复制代码
import ReactDom from 'react-dom';
import React from 'react';
import Container from './Main/Container.jsx';
import store from './store.js';
import { Provider } from 'react-redux';
ReactDom.render(
<Provider store={store}> <Container /> </Provider>
, document.getElementById('root'));
复制代码
当控制台看到[WDS] Hot Module Replacement enabled.
表明开启成功
ESLint 是众多 Javascript Linter 中的其中一种,其余比较常见的还有 JSLint 跟 JSHint,之因此用 ESLint 是由于他能够自由选择要使用哪些规则,也有不少现成的 plugin 可使用,另外他对 ES6 还有 JSX 的支持程度跟其余 linter 相比之下也是最高的。
npm install eslint eslint-loader babel-eslint --save
复制代码
其中eslint-loader
是将webpack和eslint结合起来在webpack的配置文件中新增一个eslint-loader种,修改以下
{ test: /\.(js|jsx)$/, use: [{loader:'babel-loader'},{loader:'eslint-loader'}] ,include: path.resolve(srcRoot)},
复制代码
.eslintrc
配置文件,将parser配置成babel-eslint
{
"extends": ["eslint:recommended"],
"parser": "babel-eslint",
"globals": {
},
"rules": {
}
}
复制代码
npm install eslint-plugin-react --save
复制代码
rule
下面配置,若是什么都不配置,其实自己eslint是不生效的。extends
来配置,默承认以使用eslint:recommended
。eslint-plugin-react
来告知使用react专用的规则来lint。.eslintrc
配置文件,增长rules,更多rules配置能够参考这里{
"extends": ["eslint:recommended","plugin:react/recommended"],
"parser": "babel-eslint",
"globals": {
"window": true,
"document": true,
"module": true,
"require": true
},
"rules": {
"react/prop-types" : "off",
"no-console" : "off"
}
}
复制代码
react-router强大指出在于方便代码管理,结合redux使用更增强大,同时支持web,native更多参考这里
npm install react-router-dom --save
复制代码
npm install react-router-redux@next history --save
复制代码
index.js
:import ReactDom from 'react-dom';
import React from 'react';
import Container from './Main/Container.jsx';
import { store, history } from './store.js';
import { Provider } from 'react-redux';
import createHistory from 'history/createHashHistory';
import { ConnectedRouter } from 'react-router-redux';
const history = createHistory();
ReactDom.render(
<Provider store={store}> <ConnectedRouter history={history}> <Container /> </ConnectedRouter> </Provider>
, document.getElementById('root'));
复制代码
结合history
,react-router一共有3中不一样的router:
history/createBrowserHistory
引入:当切换时,url会动态更新,底层使用的时html5的pushState。history/createHashHistory
引入:当切换时,动态修改hash,利用hashchange事件。history/createMemoryHistory
引入:将路径,路由相关数据存入内存中,不涉及url相关更新,兼容性好。更多配置能够参考这里
router-reducer
:main.js
:import { combineReducers } from 'redux';
import { routerReducer } from "react-router-redux";
import todoReducer from './todoReducer.js';
const reducers = combineReducers({
todoReducer,
router: routerReducer
});
export default reducers;
复制代码
修改store.js
:
import { createStore } from 'redux';
import mainReducer from './reducers/main.js';
const store = createStore(mainReducer);
export default store;
复制代码
而后就能够在this.props.router
里面获取单相关的路径信息 4. 若是须要本身经过action来触发router的跳转,须要引入routerMiddleware
:
import { createStore,applyMiddleware } from 'redux';
import { routerMiddleware } from "react-router-redux";
const middleware = routerMiddleware(history);
const store = createStore(mainReducer,applyMiddleware(middleware));
复制代码
Route
和Link
和withRouter
:<Route exact path="/" component={Div1}></Route>
<Route path="/2" component={Div2}></Route>
复制代码
activeClass
表示当前tab处于激活态时应用上的class。export default withRouter(connect(
state => ({
todoList: state.todoReducer.todoList
})
)(Main));
复制代码
若是你在使用hash时遇到Warning: Hash history cannot PUSH the same path; a new entry will not be added to the history stack
错误,能够将push改成replace即
<NavLink
replace={true}
to="/2"
activeClassName="selected"
>切换到2号</NavLink>
复制代码
BrowserRouter
和HashRouter
:const history = createHistory();
history.push('2');
复制代码
MemoryRouter
:const history = createMemoryHistory({
initialEntries: ['/2']
});
复制代码
redux-thunk 是一个比较流行的 redux 异步 action 中间件,好比 action 中有 setTimeout 或者经过 fetch通用远程 API 这些场景,那么久应该使用 redux-thunk 了。redux-thunk 帮助你统一了异步和同步 action 的调用方式,把异步过程放在 action 级别解决,对 component 没有影响。
redux-thunk
:npm install redux-thunk --save
复制代码
store.js
:import { createStore,applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import mainReducer from './reducers/main';
...
const store = createStore(mainReducer, applyMiddleware(thunk));
...
export default store;
复制代码
action.js
使用redux-thunk:export const getData = (obj) => (dispatch, getState) => {
setTimeout(()=>{
dispatch({
type: GET_DATA,
obj: obj
});
},1000);
};
复制代码
未完。。。
关于总结目前还在不断补充和完善当中,笔者结合相关移动web开发总结开发了一门移动web课程:
移动Web APP开发之实战美团外卖,推荐你们听一下。