從零配置React全家桶

在熟練的使用各類腳手架工具生成項目的同時,你是否還記得項目最初的樣子。本文將從零開始,帶領你們搭建一個React全家桶系列的項目結構,在完成功能的同時,會講解具體每一項的配置的含義以及延伸的知識點。若是你之後在項目配置中遇到什麼問題,不妨回來看看,或許能找到答案。javascript

前端工程化至上css

目錄

  1. 版本說明
  2. 目錄結構
  3. 初始化項目
  4. webpack
  5. react
  6. 配置loader(sass,jsx)
  7. 引入babel
  8. 使用HtmlWebpackPlugin
  9. 使用webpack-dev-server
  10. 多入口頁面配置
  11. 如何理解entry point(bundle),chunk,module
  12. 多入口頁面html配置
  13. redux
  14. 使用react-router
  15. 使用redux-thunk
  16. 模塊熱替換(Hot Module Replacement)
  17. 使用ESLint
  18. 使用axios和async/await
  19. Code Splitting
  20. 使用CommonsChunkPlugin

版本說明

因爲構建相關例如webpack,babel,react-router等更新的較快,因此本文檔如下面各類模塊的版本號爲主,各位安裝的時候須要注意一下或者能夠直接執行npm i將模塊都安裝好。html

"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.6.0",
    "react-dom": "^16.6.0",
    "react-hot-loader": "^4.3.12",
    "react-redux": "^6.0.0",
    "react-router": "^4.3.1",
    "react-router-dom": "^4.3.1",
    "redux": "^4.0.1",
    "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"
}
複製代碼

目錄結構

開發和發佈版本的配置文件是分開的,多入口頁面的目錄結構。前端

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配置文件
複製代碼

初始化項目

  1. 建立文件夾
mkdir react-family-bucket
複製代碼
  1. 初始化npm
cd react-family-bucket
npm init
複製代碼

若是有特殊須要,能夠填入本身的配置,一路回車下來,會生成一個package.json,裏面是你項目的基本信息,後面的npm依賴安裝也會配置在這裏。vue

webpack

  1. 安裝webpack
npm install webpack --save
or
npm install webpack --g
複製代碼

--save是將當前webpack安裝到react-family-bucket下的/node_modules
--g是將當前webpack安裝到全局下面,能夠在node的安裝目錄下找到全局的/node_moduleshtml5

  1. 配置webopack配置文件
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中添加執行命令:

"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

react

  1. 安裝react
npm install react react-dom --save
複製代碼
  1. 建立page目錄和index頁面文件:
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>
複製代碼
  1. 建立Main組件
import React from 'react';

class Main extends React.Component {

    constructor(props) {
        super(props);

    }

    render() {

        return (<div>Main</div>);
    }
}

export default Main;
複製代碼
  • exportexport 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';
複製代碼
  • exportmodule.exports
let exports = module.exports;
複製代碼
  1. 修改webpack配置入口文件
entry: [
    path.resolve(srcRoot,'./page/index/index.js')
],
複製代碼

配置loader

  1. 處理樣式文件須要這些loader:
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)}
        ]
    },
複製代碼
  1. url-loader處理處理靜態文件
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 具體參考這裏

引入babel

bebel是用來解析es6語法或者是es7語法分解析器,讓開發者可以使用新的es語法,同時支持jsx,vue等多種框架。

  1. 安裝babel
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
複製代碼
  1. babel-polyfill是什麼?
    咱們以前使用的babel,babel-loader 默認只轉換新的 JavaScript 語法,而不轉換新的 API。例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局對象,以及一些定義在全局對象上的方法(好比 Object.assign)都不會轉譯。若是想使用這些新的對象和方法,必須使用 babel-polyfill,爲當前環境提供一個墊片。
npm install --save babel-polyfill
複製代碼

使用:

import "babel-polyfill";
複製代碼
  1. transform-runtime有什麼區別?
    當使用babel-polyfill時有一些問題:
  • 默認會引入全部babel支持的新語法,這樣就會致使你的文件代碼很是龐大。
  • 經過向全局對象和內置對象的prototype上添加方法來達成目的,形成全局變量污染。

這時就須要transform-runtime來幫咱們有選擇性的引入

npm install --save babel-plugin-transform-runtime
複製代碼

配置文件:

{
  "plugins": [
    ["transform-runtime", {
      "helpers": false,
      "polyfill": false,
      "regenerator": true,
      "moduleName": "babel-runtime"
    }]
  ]
}
複製代碼

使用HtmlWebpackPlugin

記得咱們以前新建的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文件爲模版 更多參數配置能夠參考這裏

使用webpack-dev-server

webpack-dev-server是一個小型的Node.js Express服務器,它使用webpack-dev-middleware來服務於webpack的包。

  1. 安裝
npm install webpack-dev-server --save
複製代碼

修改在package.json中添加的執行命令:

"scripts": {
    "dev": "./node_modules/.bin/webpack-dev-server --config webpack.config.dev.js",
  },
複製代碼
  1. 配置webpack配置文件:
devServer: {
    "contentBase": devPath,
    "compress": true,
},
複製代碼

contentBase 表示server文件的根目錄 compress 表示開啓gzip 更多的配置文檔參考這裏

  • webpack-dev-server默認狀況下會將output的內容放在內存中,是看不到物理的文件的,若是想要看到物理的dev下面的文件能夠安裝write-file-webpack-plugin這個插件。

  • webpack-dev-server默認會開啓livereload功能

  1. devtool功能:
    具體來講添加了devtool: 'inline-source-map'以後,利用source-map你在chrome控制檯看到的source源碼都是真正的源碼,未壓縮,未編譯前的代碼,沒有添加,你看到的代碼是真實的壓縮過,編譯過的代碼,更多devtool的配置能夠參考這裏

多入口文件配置

在以前的配置中,都是基於單入口頁面配置的,entry和output只有一個文件,可是實際項目不少狀況下是多頁面的,在配置多頁面時,有2中方法能夠選擇:

  1. 在entry入口配置時,傳入對象而不是單獨數組,output時利用[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'
},
複製代碼
  1. 經過node動態遍歷須要entry point的目錄,來動態生成entry:
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?先看看下圖:

圖片描述

根據圖上的表述,我這裏簡單說一下結論:

  • 配置中每一個文件例如index1.js,index2.js,detail.js,home.js都屬於entry point.
  • entry這個配置中,每一個key值,index,detail,home都至關於chunk
  • 咱們在代碼中的require或者import的都屬於module,這點很好理解。
  • chunk的分類比較特別,有entry chunk,initial chunk,normal chunk,參考這個文章
  • 正常狀況下,一個chunk對應一個output,在使用了CommonsChunkPlugin或者require.ensure以後,chunk就變成了initial chunk,normal chunk,這時,一個chunk對應多個output。
    理解這些概念對於後續使用webpack插件有很大的幫助。

多入口頁面html配置

以前咱們配置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)
複製代碼

redux

關於redux的使用能夠參考阮一峯老師的入門教程

  1. 安裝redux
npm install redux react-redux --save
複製代碼

須要注意,若是react-redux使用的是7.0.3版本的,須要安裝16.8.3以上版本的react。

  1. 新建reducersactions目錄和文件
|—— index/                          
|—— Main/                   * 組件代碼
|       |—— Main.jsx        * 組件jsx
|       |—— Main.scss       * 組件css
|
|—— actions/ 
|       |—— actionTypes.js  * action常量
|       |—— todoAction.js   * action
|
|—— reducers/ 
|       |—— todoReducer.js  * reducer
|       |—— main.js         * 將全部reducer進行combin,store.js只引入這個
|
|—— store.js
|
|—— index.js

複製代碼
  1. 修改代碼,引入redux,這裏以一個redux todo爲demo例子:

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
  };
};
複製代碼

使用react-router

react-router強大指出在於方便代碼管理,結合redux使用更增強大,同時支持web,native更多參考這裏

  1. 安裝react-router-dom
npm install react-router-dom --save
複製代碼
  1. 若是項目中用了redux,能夠安裝connected-react-router
npm install connected-react-router history --save
複製代碼
  1. 修改代碼:
    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 { ConnectedRouter } from 'connected-react-router';

ReactDom.render(
    <Provider store={store}> <ConnectedRouter history={history}> <Container /> </ConnectedRouter> </Provider>
, document.getElementById('root'));
複製代碼

結合history,react-router一共有3中不一樣的router:

  • BrowserRouter經過history/createBrowserHistory引入:當切換時,url會動態更新,底層使用的時html5的pushState
  • HashRouter經過history/createHashHistory引入:當切換時,動態修改hash,利用hashchange事件。
  • MemoryRouter經過history/createMemoryHistory引入:將路徑,路由相關數據存入內存中,不涉及url相關更新,兼容性好。

更多配置能夠參考這裏

  1. 若是想要在代碼邏輯中獲取當前的route的location信息須要在main.js中添加router的reducer:
    新建main.js:
import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router'
import todoReducer from './todoReducer.js';

const reducers = (history) => combineReducers({
  todoReducer,
  router: connectRouter(history)
});
export default reducers;
複製代碼

編寫store.js:

import { createStore } from 'redux';
import mainReducer from './reducers/main.js';
import createHistory from 'history/createHashHistory';

export const history = createHistory();

const store = createStore(
        createRootReducer(history),
    );

export default store;
複製代碼

而後就能夠在this.props.router裏面獲取到相關的location路徑信息,其實就是將router對象放在組件的props裏面。

  1. 若是須要本身經過dispatch一個action來觸發router的跳轉,須要引入routerMiddleware:
import { createStore,applyMiddleware,compose } from 'redux';
import mainReducer from './reducers/main.js';
import createHistory from 'history/createHashHistory';
import { routerMiddleware } from "connected-react-router";

export const history = createHistory();


const store = createStore(
        createRootReducer(history),
        compose(applyMiddleware(routerMiddleware(history)))
    );

export default store;
複製代碼

最後,將改動同步在index.js長這樣:

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 { ConnectedRouter } from 'connected-react-router';

ReactDom.render(
    <Provider store={store}> <ConnectedRouter history={history}> <Container /> </ConnectedRouter> </Provider>
, document.getElementById('root'));
複製代碼

看到這裏,你們可能會對react-router,react-router-dom,connected-react-router,history這些東西給搞暈了,在這裏解釋一下:

  • react-router:是react-router的核心邏輯,並不涉及到UI,必須引入。

  • react-router-dom:是react-router的DOM綁定模塊,只有用了react-router-dom才能用相似,這些組件,必須引入。

  • history:是一個讓你靈活控制或者模擬瀏覽器的歷史操做的一個庫,包括BrowserHistory,HashHistory,MemoryHistory,他能夠脫離React使用,也能夠結合React使用,可選引入。

  • connected-react-router:簡單來講就是你的項目若是用的React和Redux,若是想要用router的話就能夠引入connected-react-router,它能幫你把router的狀態放在store裏來管理(注意根據你使用的react-router版本不一樣,會使用不一樣的版本,它的前身是react-router-redux,官方已經再也不維護了),可選引入。

  1. 使用RouteLinkwithRouter:
    先說說都是幹嗎的:
  • Route:component裏面的內容便是tab的主要內容,這個從react-router4開始生效:
<Route exact path="/" component={Div1}></Route>
<Route path="/2" component={Div2}></Route>
複製代碼
  • Link:一般也能夠用NavLink,至關於tab按鈕,控制router的切換,activeClass表示當前tab處於激活態時應用上的class。
  • withRouter:若是你用了redux,那麼你必定要引入它。
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>
複製代碼
  1. 設置初始化路由:
  • BrowserRouterHashRouter:
const history = createHistory();
history.push('2');
複製代碼
  • MemoryRouter:
const history = createMemoryHistory({
    initialEntries: ['/2']
});
複製代碼

使用redux-thunk

redux-thunk 是一個比較流行的 redux 異步 action 中間件,好比 action 中有 setTimeout 或者經過 fetch通用遠程 API 這些場景,那麼久應該使用 redux-thunk 了。redux-thunk 幫助你統一了異步和同步 action 的調用方式,把異步過程放在 action 級別解決,對 component 沒有影響。

  1. 安裝redux-thunk:
npm install redux-thunk --save
複製代碼
  1. 修改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;
複製代碼
  1. action.js使用redux-thunk:
export const getData = (obj) => (dispatch, getState) => {
  setTimeout(()=>{
    dispatch({
        type: GET_DATA,
        obj: obj
    });
  },1000);
};
複製代碼

模塊熱替換(Hot Module Replacement)

模塊熱替換(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一塊兒使用:

  1. 安裝react-hot-loader,
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);
    });
}
複製代碼
  1. 修改index.js
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

ESLint 是衆多 Javascript Linter 中的其中一種,其餘比較常見的還有 JSLintJSHint,之因此用 ESLint 是由於他能夠自由選擇要使用哪些規則,也有不少現成的 plugin 可使用,另外他對 ES6 還有 JSX 的支持程度跟其餘 linter 相比之下也是最高的。

  1. 安裝ESLint
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)},
複製代碼
  1. 新建.eslintrc配置文件,將parser配置成babel-eslint
{
    "extends": ["eslint:recommended"],
    
    "parser": "babel-eslint",

    "globals": {
    },
    "rules": {
    }
}
複製代碼
  1. 安裝eslint-plugin-react:
npm install eslint-plugin-react --save
複製代碼
  • 說明一下,正常狀況下每一個eslint規則都是須要在rule下面配置,若是什麼都不配置,其實自己eslint是不生效的。
  • eslint自己有不少默認的規則模版,能夠經過extends來配置,默承認以使用eslint:recommended
  • 在使用react開發時能夠安裝eslint-plugin-react來告知使用react專用的規則來lint。
  1. 修改.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"
    }
}
複製代碼

使用axios和async/await

axios 是一個基於Promise 用於瀏覽器和 nodejs 的 HTTP 客戶端:

  • 從瀏覽器中建立 XMLHttpRequest
  • 從 node.js 發出 http 請求
  • 支持 Promise API
  • 自動轉換JSON數據
  1. 安裝axios:
npm install axios --save
複製代碼
  1. 在action中使用axios:
import axios from 'axios';
export const getData = (obj) => (dispatch, getState) => {
    axios.get('/json/comments.json').then((resp)=>{
        dispatch({
            type: GET_DATA,
            obj: resp
        });
    });
};
複製代碼

async/await

Javascript的回調地獄,相信不少人都知道,尤爲是在node端,近些年比較流行的是Promise的解決方案,可是隨着 Node 7 的發佈,編程終級解決方案的 async/await應聲而出。

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

async function asyncCall() {
  var result = await resolveAfter2Seconds();
}

asyncCall();
複製代碼

async/await的用途是簡化使用 promises 異步調用的操做,並對一組 Promises執行某些操做。await前提是方法返回的是一個Promise對象,正如Promises相似於結構化回調,async/await相似於組合生成器和 promises。

  1. async/await須要安裝babel-plugin-transform-async-to-generator
npm install babel-plugin-transform-async-to-generator --save
複製代碼
  1. .babelrc中增長配置:
"plugins": [
        "transform-async-to-generator"
    ]
複製代碼

這樣作僅僅是將async轉換generator,若是你當前的瀏覽器不支持generator,你將會收到一個Uncaught ReferenceError: regeneratorRuntime is not defined的錯誤,你須要:
3. 安裝babel-plugin-transform-runtime:

npm install babel-plugin-transform-async-to-generator --save
複製代碼
  1. 修改.babelrc中的配置(能夠去掉以前配置的transform-async-to-generator):
"plugins": [
        "transform-runtime"
    ]
複製代碼
  1. 若是不想引入全部的polyfill(參考上面對babel的解釋),能夠增長配置:
"plugins": [
        "transform-runtime",
            {
                "polyfill": false,

                "regenerator": true,
            }
    ]
複製代碼
  1. 結合axios使用:
import axios from 'axios';
export const getData = (obj) => async (dispatch, getState) => {
    let resp = axios.get('/json/comments.json');
    dispatch({
        type: GET_DATA,
        obj: resp
    });
};
複製代碼

Code Splitting

  1. 對於webpack1,2以前,你可使用require.ensure來控制一個組件的懶加載:
require.ensure([], _require => {
    let Component = _require('./Component.jsx');
},'lazyname')
複製代碼
  1. 在webpack4中,官方已經再也不推薦使用require.ensure來使用懶加載功能Dynamic Imports,取而代之的是ES6的import()方法:
import(
  /* webpackChunkName: "my-chunk-name" */
  /* webpackMode: "lazy" */
  'module'
);
複製代碼

不小小看註釋裏的代碼,webpack在打包時會動態識別這裏的代碼來作相關的配置,例如chunk name等等。 3. Prefetching/Preloading modules:

webpack 4.6.0+支持了Prefetching/Preloading的寫法:

//...
import(/* webpackPreload: true */ 'ChartingLibrary');
複製代碼
  1. 結合React-Router使用:

react-loadable對上述的功能作了封裝,豐富了一些功能,結合React-Router起來使用更加方便。

npm install react-loadable --save
複製代碼

在react-router裏使用:

function Loading() {
  return <div>Loading...</div>;
}

let Div2 = Loadable({
  loader: () => import('./Div2'), 
  loading: Loading,
});

<Route path="/2" component={Div2}></Route>
複製代碼

使用CommonsChunkPlugin

CommonsChunkPlugin 插件,是一個可選的用於創建一個獨立文件(又稱做 chunk)的功能,這個文件包括多個入口 chunk 的公共模塊。經過將公共模塊拆出來,最終合成的文件可以在最開始的時候加載一次,便存起來到緩存中供後續使用。

  1. 在webpack4以前的用法:
new webpack.optimize.CommonsChunkPlugin({
    name: 'common',
    chunks: ['page1','page2'],
    minChunks: 3
})
複製代碼
  • name: string: 提取出的名稱。
  • chunks: string[]: webpack會從傳入的chunk裏面提取公共代碼,默認從全部entry裏提取。
  • minChunks: number|infinity|function(module,count)->boolean: 若是傳入數字或infinity(默認值爲3),就是告訴webpack,只有當模塊重複的次數大於等於該數字時,這個模塊纔會被提取出來。當傳入爲函數時,全部符合條件的chunk中的模塊都會被傳入該函數作計算,返回true的模塊會被提取到目標chunk。 更多的參數配置,能夠參考這裏
  1. 在webpack4以後的用法:
module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
};
複製代碼
  • splitChunks: 配置一個分離chunk(代替老版本的CommonsChunkPlugin)。
  • cacheGroups: 自定義配置主要使用它來決定生成的文件:
    1. test: 限制範圍。
    2. name: 生成文件名。
    3. priority: 優先級。
  • minSize: number: 最小尺寸必須大於此值,默認30000B。
  • minChunks: 其餘entry引用次數大於此值,默認1。
  • maxInitialRequests: entry文件請求的chunks不該該超過此值(請求過多,耗時)。
  • maxAsyncRequests: 異步請求的chunks不該該超過此值。
  • automaticNameDelimiter: 自動命名鏈接符
  • chunks: 值爲"initial", "async"(默認) 或 "all":
    1. initial: 入口chunk,對於異步導入的文件不處理。
    2. async: 異步chunk,只對異步導入的文件處理。
    3. all: 所有chunk。

講到這裏,就基本完結了,另外本文檔會實時根據新版本的升級而更新。若是各位對React全家桶感興趣,不妨學一學這門課程《移動Web App開發之實戰美團外賣》

相關文章
相關標籤/搜索