git clone git@github.com:xiehaitao0229/react-wepack4-xht.git
cd react-webpack4-xht
css
`npm run dev` // 啓動本地
`npm run build` // 打包線上環境
`npm run clean` // 清除線上環境打包出來的文件
`npm run test` // 單元測試的工具庫
`npm run fix` // 修復eslint的寫法
`npm run format` // 格式化代碼
`npm run precommit` // commit 代碼到git倉庫的檢查
複製代碼
webpack今年推出的4這個版本就一直關注很學習它,webpack4這個版本借鑑了parcel的零配置,打包速度變得更快,值得你們去跟進學習。html
既然咱們已經迎接了webpack4的到來了,那麼就一塊兒來使用一下,即便你沒用過以前的版本,不要緊,咱們從新出發,將工做中經常使用到的配置寫給你們來看vue
- 須要先在項目中npm init初始化一下,生成package.json
- 建議node版本安裝到8.2以上
// webpack4中除了正常安裝webpack以外,須要再單獨安一個webpack-cli
npm i webpack webpack-cli -D
複製代碼
在項目下建立一個webpack.config.js(默認,可修改)文件來配置webpacknode
module.exports = {
entry: '', // 入口文件
output: {}, // 出口文件
module: {}, // 處理對應模塊
plugins: [], // 對應的插件
devServer: {}, // 開發服務器配置
mode: 'development' // 模式配置
}
複製代碼
以上就是webpack的正常配置模塊 啓動devServer須要安裝一下webpack-dev-serverreact
npm i webpack-dev-server -D
複製代碼
接下來咱們按照項目的結構,咱們就從0開始去寫一下配置
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js', // 入口文件
output: {
filename: 'bundle.js', // 打包後的文件名稱
path: path.resolve('dist') // 打包後的目錄,必須是絕對路徑
}
}
複製代碼
上面就能夠說是實現了最簡單的webpack配置了,那接下來就打包一下看看 webpack
工做當中咱們打包編譯的時候通常都執行npm run dev這樣的命令,既然是經過npm執行的命令,咱們就應該找到package.json裏的執行腳本去配置一下命令,這裏以下圖所示 git
npm run build就是咱們打包後的文件,這是生產環境下,上線須要的文件npm run dev是咱們開發環境下打包的文件,固然因爲devServer幫咱們把文件放到內存中了,因此並不會輸出打包後的dist文件夾es6
文件都打包好了,可是咱們在使用的時候不能在dist目錄下去建立一個html文件,而後去引用打包後的js吧,這不合理,實際開發中也不會這樣 咱們須要實現html打包功能,能夠經過一個模板實現打包出引用好路徑的html來 這就須要用到一個經常使用的插件了,< html-webpack-plugin >,用以前咱們來安一下它github
npm i html-webpack-plugin -D
複製代碼
let path = require('path');
// 插件都是一個類,因此咱們命名的時候儘可能用大寫開頭
let HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
// 添加hash能夠防止文件緩存,每次都會生成4位的hash串
filename: 'bundle.js',
path: path.resolve('dist')
},
plugins: [
// 經過new一下這個類來使用插件
new HtmlWebpackPlugin({
// 用哪一個html做爲模板
// 在src目錄下建立一個index.html頁面當作模板來用
template: './src/index.html',
hash: true, // 會在打包好的bundle.js後面加上hash串
})
]
}
複製代碼
經過上面的配置後,咱們再npm run build打包看一下如今是個什麼樣子了 web
多頁面開發,怎麼配置多頁面 若是開發的時候不僅一個頁面,咱們須要配置多頁面,那麼須要怎麼來搞呢?不用擔憂,html-webpack-plugin插件自有辦法,咱們來觀望一下let path = require('path');
let HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 多頁面開發,怎麼配置多頁面
entry: {
index: './src/index.js',
login: './src/login.js'
},
// 出口文件
output: {
filename: '[name].js',
path: path.resolve('dist')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
chunks: ['index'] // 對應關係,index.js對應的是index.html
}),
new HtmlWebpackPlugin({
template: './src/index2.html',
filename: 'login.html',
chunks: ['login'] // 對應關係,login.js對應的是login.html
})
]
}
複製代碼
上面基本介紹完了html和js的打包配置了,webpack對css的解析須要用到loader,因此咱們先提早安裝好,待會好方便使用
須要下載一些解析css樣式的loader
npm i style-loader css-loader -D
// 引入less文件的話,也須要安裝對應的loader
npm i less less-loader -D
npm i node-sass sass-loader -D
複製代碼
下面咱們來看一下如何配置css文件的解析
// index.js
import './css/style.css'; // 引入css
import './less/style.less'; // 引入less
console.log('這裏是打包文件入口-index.js');
// webpack.config.js
module.exports = {
entry: {
index: './src/index.js'
},
output: {
filename: 'bundle.js',
path: path.resolve('dist')
},
module: {
rules: [
{
test: /\.css$/, // 解析css
use: ['style-loader', 'css-loader'] // 從右向左解析
/*
也能夠這樣寫,這種方式方便寫一些配置參數
use: [
{loader: 'style-loader'},
{loader: 'css-loader'}
]
*/
}
]
}
}
複製代碼
- 此時打包後的css文件是以行內樣式style的標籤寫進打包後的html頁面中,若是樣式不少的話,咱們更但願直接用link的方式引入進去,這時候須要把css拆分出來
- extract-text-webpack-plugin插件它的功效就在於會將打包到js裏的css文件進行一個拆分,單獨提取css
// @next表示能夠支持webpack4版本的插件
npm i extract-text-webpack-plugin@next -D
複製代碼
let path = require('path');
let HtmlWebpackPlugin = require('html-webpack-plugin');
// 拆分css樣式的插件
let ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');
module: {
rules: [
{
test: /\.less$/, // 解析less
use: ExtractTextWebpackPlugin.extract({
// 將css用link的方式引入就再也不須要style-loader了
fallback: "style-loader",
use: ['css-loader', 'less-loader'] // 從右向左解析
})
},
{
test: /\.scss$/, // 解析scss
use: ExtractTextWebpackPlugin.extract({
// 將css用link的方式引入就再也不須要style-loader了
fallback: "style-loader",
use: ['css-loader', 'sass-loader'] // 從右向左解析
})
},
{
test: /\.css$/, // 解析css
use: ExtractTextWebpackPlugin.extract({
// 將css用link的方式引入就再也不須要style-loader了
fallback: "style-loader",
use: ['css-loader']
})
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
// 拆分後會把css文件放到dist目錄下的css/style.css
new ExtractTextWebpackPlugin('css/style.css')
]
複製代碼
另外一個插件mini-css-extract-plugin也是能夠辦到的,它能夠說是爲webpack4而生的, 在這裏就簡單的提一下
npm i mini-css-extract-plugin -D
複製代碼
let MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/a.css' // 指定打包後的css
})
]
}
複製代碼
這裏要着重說一下上面兩個插件的區別了,我的仍是建議用extract-text-webpack-plugin的,畢竟從以前的版本承接下來的,雖然在安包的時候須要@next,可是仍是值得信賴的
並且如今的extract-text-webpack-plugin也支持了拆分紅多個css,而目前mini-css-extract-plugin還不支持此功能
// 正常寫入的less
let styleLess = new ExtractTextWebpackPlugin('css/style.css');
// reset
let resetCss = new ExtractTextWebpackPlugin('css/reset.css');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: resetCss.extract({
fallback: "style-loader",
use: 'css-loader'
})
},
{
test: /\.less$/,
use: styleLess.extract({
fallback: "style-loader",
use: ['css-loader', 'less-loader'] // 從右向左解析
})
}
]
},
plugins: [
styleLess,
resetCss
]
}
複製代碼
經過這樣操做後能夠打包成兩個不一樣的css文件,以下圖
npm i file-loader url-loader -D
複製代碼
若是是在css文件裏引入的如背景圖之類的圖片,就須要指定一下相對路徑
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192, // 小於8k的圖片自動轉成base64格式,而且不會存在實體圖片
outputPath: 'images/' // 圖片打包後存放的目錄
}
}
]
}
]
}
}
複製代碼
在css中指定了publicPath路徑這樣就能夠根據相對路徑引用到圖片資源了,以下圖所示
頁面中常常會用到img標籤,img引用的圖片地址也須要一個loader來幫咱們處理好
npm i html-withimg-loader -D
複製代碼
module.exports = {
module: {
rules: [
{
test: /\.(htm|html)$/,
use: 'html-withimg-loader'
}
]
}
}
複製代碼
這樣再打包後的html文件下img就能夠正常引用圖片路徑了
字體圖標和svg圖片均可以經過file-loader來解析
module.exports = {
module: {
rules: [
{
test: /\.(eot|ttf|woff|svg)$/,
use: 'file-loader'
}
]
}
}
複製代碼
這樣即便樣式中引入了這類格式的圖標或者圖片都沒有問題了,img若是也引用svg格式的話,配合上面寫好的html-withimg-loader就都沒有問題了
經過postcss中的autoprefixer能夠實現將CSS3中的一些須要兼容寫法的屬性添加響應的前綴,這樣省去咱們很多的時間
因爲也是一個loader加載器,咱們也須要先安裝一下
npm i postcss-loader autoprefixer -D
複製代碼
安裝後,咱們還須要像webpack同樣寫一個config的配置文件,在項目根目錄下建立一個postcss.config.js文件,配置以下:
module.exports = {
plugins: [
require('autoprefixer')({
"browsers": [
"defaults",
"not ie < 11",
"last 2 versions",
"> 1%",
"iOS 7",
"last 3 iOS versions"
]
})
]
};
複製代碼
而後在webpack裏配置postcss-loader
module.exports = {
module: {
rules: [
{
test: /\.less$/, // 解析less
use: ExtractTextWebpackPlugin.extract({
// 將css用link的方式引入就再也不須要style-loader了
fallback: "style-loader",
use: ['css-loader', 'postcss-loader', 'less-loader'] // 從右向左解析
})
},
{
test: /\.scss$/, // 解析scss
use: ExtractTextWebpackPlugin.extract({
// 將css用link的方式引入就再也不須要style-loader了
fallback: "style-loader",
use: ['css-loader', 'postcss-loader', 'sass-loader'] // 從右向左解析
})
},
{
test: /\.css$/, // 解析css
use: ExtractTextWebpackPlugin.extract({
// 將css用link的方式引入就再也不須要style-loader了
fallback: "style-loader",
use: ['css-loader', 'postcss-loader']
})
},
]
}
}
複製代碼
在實際開發中,咱們在大量的使用着ES6及以後的api去寫代碼,這樣會提升咱們寫代碼的速度,不過因爲低版本瀏覽器的存在,不得不須要轉換成兼容的代碼,因而就有了經常使用的Babel了
Babel會將ES6的代碼轉成ES5的代碼
npm i babel-core babel-loader babel-preset-env babel-preset-stage-3 babel-preset-react babel-polyfill babel-plugin-import babel-loader babel-register -D
babel-preset-stage-3 使用這個插件來編譯爲了後面使用...state擴展運算符可使用
複製代碼
當把這些都安好後,咱們就開始配置,因爲要兼容的代碼不只僅包含ES6還有以後的版本和那些僅僅是草案的內容,因此咱們能夠經過一個.babelrc文件來配置一下,對這些版本的支持
// .babelrc
{
"presets": [
[
"env",
{
"loose": true,
"modules": false
}
],
"es2015",
"react",
"babel-preset-stage-3"
]
}
複製代碼
咱們再在webpack裏配置一下babel-loader既能夠作到代碼轉成ES5了
module.exports = {
module: {
rules: [
{
test:/\.js$/,
use: 'babel-loader',
include: /src/, // 只轉化src目錄下的js
exclude: /node_modules/ // 排除掉node_modules,優化打包速度
}
]
}
}
複製代碼
1.先來講說babel-plugin-transform-runtime
在轉換 ES2015 語法爲 ECMAScript 5 的語法時,babel 會須要一些輔助函數,例如 _extend。babel 默認會將這些輔助函數內聯到每個 js 文件裏,這樣文件多的時候,項目就會很大。
因此 babel 提供了 transform-runtime 來將這些輔助函數「搬」到一個單獨的模塊 babel-runtime 中,這樣作能減少項目文件的大小。
npm install --save-dev babel-plugin-transform-runtime
複製代碼
修改.babelrc配置文件,增長配置 .babelrc
"plugins": [
"transform-runtime"
]
複製代碼
2.再來看babel-polyfill 爲何要集成babel-polyfill?
Babel默認只轉換新的JavaScript句法(syntax),而不轉換新的API,好比Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局對象,以及一些定義在全局對象上的方法(好比Object.assign)都不會轉碼。 舉例來講,ES6在Array對象上新增了Array.from方法。Babel就不會轉碼這個方法。若是想讓這個方法運行,必須使用babel-polyfill,爲當前環境提供一個墊片。
npm install --save-dev babel-polyfill
複製代碼
// 修改入口文件index.js
import 'babel-polyfill';
複製代碼
呀呀呀,在咱們每次npm run build的時候都會在dist目錄下建立不少打好的包,若是積累過多可能也會混亂
因此應該在每次打包以前將dist目錄下的文件都清空,而後再把打好包的文件放進去,主人們,接下來咱們使用clean-webpack-plugin這個插件吧
npm i clean-webpack-plugin -D
複製代碼
let CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
plugins: [
// 打包前先清空
new CleanWebpackPlugin('dist')
]
}
複製代碼
resolve: {
// 別名
alias: {
pages:path.join(__dirname,'src/pages'),
component:path.join(__dirname,'src/component'),
actions:path.join(__dirname,'src/redux/actions'),
reducers:path.join(__dirname,'src/redux/reducers'),
},
// 省略後綴
extensions: ['.js', '.jsx', '.json', '.css', '.scss', '.less']
}
複製代碼
在webpack4以前,提取公共代碼都是經過一個叫CommonsChunkPlugin的插件來辦到的。到了4之後,內置了一個如出一轍的功能 optimization
下面咱們就來看看如何提取公共代碼
optimization: {
splitChunks: {
cacheGroups: {
vendor: { // 抽離第三方插件
test: /node_modules/, // 指定是node_modules下的第三方包
chunks: 'initial',
name: 'vendor', // 打包後的文件名,任意命名
// 設置優先級,防止和自定義的公共代碼提取時被覆蓋,不進行打包
priority: 10
},
utils: {
// 抽離本身寫的公共代碼,utils裏面是一個公共類庫
chunks: 'initial',
name: 'utils', // 任意命名
minSize: 0 // 只要超出0字節就生成一個新包
}
}
}
},
還要在plugins裏面引入須要單獨打包出來的chunk
new HtmlWebpackPlugin({
template: './src/index.html',
chunks: ['vendor', 'index', 'utils'] // 引入須要的chunk
}),
複製代碼
簡單來講,webpack-dev-server就是一個小型的靜態文件服務器。使用它,能夠爲webpack打包生成的資源文件提供Web服務。
npm install webpack-dev-server --save-dev
npm install webpack-dev-server -g
複製代碼
devServer: {
port: 3000, // 端口
open: true, // 自動打開瀏覽器
hot: true, // 開啓熱更新
overlay: true, // 瀏覽器頁面上顯示錯誤
historyApiFallback: true
},
複製代碼
如今咱們發現一個問題,代碼哪裏寫錯了,瀏覽器報錯只報在build.js第幾行。這讓咱們排查錯誤無從下手,傳送門。 在開發環境下配置
devtool: 'inline-source-map'
複製代碼
同時,咱們在srouce裏面能看到咱們寫的代碼,也能打斷點調試代碼
在配置devServer的時候,若是hot爲true,就表明開啓了熱更新,可是這並無那麼簡單,由於熱更新還須要配置一個webpack自帶的插件而且還要在主要js文件裏檢查是否有module.hot
// webpack.config.js
let webpack = require('webpack');
module.exports = {
plugins: [
// 熱更新,熱更新不是刷新
new webpack.HotModuleReplacementPlugin()
],
devServer: {
hot: true, // 加上這一行
}
}
// 在入口文件index.js
// 還須要在主要的js文件裏寫入下面這段代碼
if (module.hot) {
// 實現熱更新
module.hot.accept();
}
複製代碼
熱更新的好處在開發vue或者react的時候,其中某個組件修改的時候就會針對這個組件進行更新,超級好用,提高開發效率
`npm install --save react-router-dom`
複製代碼
新建router文件夾和組件
`cd src`
`mkdir router && touch router/router.js`
複製代碼
按照react-router
文檔編輯一個最基本的router.js
。包含兩個頁面home
和page1
。 src/router/router.js
import React from 'react';
import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';
import Home from '../pages/Home/Home';
import Page1 from '../pages/Page1/Page1';
const getRouter = () => (
<Router>
<div>
<ul>
<li><Link to="/">首頁</Link></li>
<li><Link to="/page1">Page1</Link></li>
</ul>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/page1" component={Page1}/>
</Switch>
</div>
</Router>
);
export default getRouter;
複製代碼
新建頁面文件夾
cd src
mkdir pages
複製代碼
新建兩個頁面 Home,Page1
cd src/pages
mkdir Home && touch Home/Home.js
mkdir Page1 && touch Page1/Page1.js
複製代碼
src/pages/Home/Home.js
import React, {Component} from 'react';
export default class Home extends Component {
render() {
return (
<div>
this is home ~hi xht
</div>
)
}
}
複製代碼
Page1.js
import React, {Component} from 'react';
export default class Page1 extends Component {
render() {
return (
<div>
this is Page1~hi xht
</div>
)
}
}
複製代碼
如今路由和頁面建好了,咱們在入口文件src/index.js引用Router。 修改src/index.js
import React from 'react';
import ReactDom from 'react-dom';
import getRouter from './router/router';
ReactDom.render(
getRouter(), document.getElementById('root'));
複製代碼
如今執行打包命令npm run dev。打開index.html查看效果啦!
接下來,咱們就要就要就要集成redux了。 要對redux
有一個大概的認識,能夠閱讀阮一峯前輩的Redux 入門教程(一):基本用法
若是要對redux
有一個很是詳細的認識,我推薦閱讀中文文檔,寫的很是好。讀了這個教程,有一個很是深入的感受,redux
並無任何魔法。 咱們就作一個最簡單的計數器。自增,自減,重置。 先安裝redux
`npm install --save redux`
複製代碼
初始化目錄結構
cd src
mkdir redux
cd redux
mkdir actions
mkdir reducers
touch reducers.js
touch store.js
touch actions/couter.js
touch reducers/couter.js
複製代碼
先來寫action建立函數。經過action建立函數,能夠建立action src/redux/actions/counter.js
/*action*/
export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";
export const RESET = "RESET";
export function increment() {
return {type: INCREMENT}
}
export function decrement() {
return {type: DECREMENT}
}
export function reset() {
return {type: RESET}
}
複製代碼
再來寫reducer,reducer是一個純函數,接收action和舊的state,生成新的state. src/redux/reducers/couter.js
import { INCREMENT, DECREMENT, RESET } from '../actions/couters';
/*
* 初始化state
*/
const initState = {
count: 0,
};
/*
* reducer
*/
export default function reducer(state = initState, action) {
switch (action.type) {
case INCREMENT:
return {
count: state.count + 1,
};
case DECREMENT:
return {
count: state.count - 1,
};
case RESET:
return { count: 0 };
default:
return state;
}
}
複製代碼
一個項目有不少的reducers,咱們要把他們整合到一塊兒 src/redux/reducers.js
import counter from './reducers/couter';
export default function combineReducers(state = {}, action) {
return {
counter: counter(state.counter, action)
}
}
複製代碼
reducer就是純函數,接收state 和 action,而後返回一個新的 state。 看上面的代碼,不管是combineReducers函數也好,仍是reducer函數也好,都是接收state和action, 返回更新後的state。區別就是combineReducers函數是處理整棵樹,reducer函數是處理樹的某一點。 接下來,咱們要建立一個store。
前面咱們可使用 action 來描述「發生了什麼」,使用action建立函數來返回action。
還可使用 reducers 來根據 action 更新 state 。
那咱們如何提交action?提交的時候,怎麼才能觸發reducers呢?
store 就是把它們聯繫到一塊兒的對象。store 有如下職責:
維持應用的 state;
- 提供 getState() 方法獲取 state;
- 提供 dispatch(action) 觸發reducers方法更新 state;
- 經過subscribe(listener) 註冊監聽器;
- 經過 subscribe(listener) 返回的函數註銷監聽器。
src/redux/store.js
import {createStore} from 'redux';
import combineReducers from './reducers.js';
let store = createStore(combineReducers);
export default store;
複製代碼
寫一個Counter頁面
cd src/pages
mkdir Counter
touch Counter/Counter.js
複製代碼
src/pages/Counter/Counter.js
import React, {Component} from 'react';
export default class Counter extends Component {
render() {
return (
<div>
<div>當前計數爲(顯示redux計數)</div>
<button onClick={() => {
console.log('調用自增函數');
}}>自增
</button>
<button onClick={() => {
console.log('調用自減函數');
}}>自減
</button>
<button onClick={() => {
console.log('調用重置函數');
}}>重置
</button>
</div>
)
}
}
複製代碼
修改路由,增長Counter src/router/router.js
import React from 'react';
import { HashRouter as Router, Route, Switch, Link } from 'react-router-dom';
import Home from 'pages/Home/Home';
import Page1 from 'pages/Page1/Page1';
import Counter from 'pages/Counter/Counter';
const getRouter = () => (
<Router>
<div>
<ul>
<li>
<Link to="/">首頁</Link>
</li>
<li>
<Link to="/page1">Page1</Link>
</li>
<li>
<Link to="/couter">Counter</Link>
</li>
</ul>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/page1" component={Page1} />
<Route path="/couter" component={Counter} />
</Switch>
</div>
</Router>
);
export default getRouter;
複製代碼
npm run dev 看看效果。 下一步,咱們讓Counter組件和react-redux
聯合起來。使Counter能得到到Redux的state,而且能發射action。 先來安裝react-redux
npm install --save react-redux
複製代碼
src/pages/Counter/Counter.js
import React, { Component } from 'react';
import { increment, decrement, reset } from 'actions/couters';
import { connect } from 'react-redux';
class Counter extends Component {
render() {
const {
counter: { count },
increment,
decrement,
reset,
} = this.props;
return (
<div>
<div>
當前計數爲:
{count}
</div>
<button onClick={() => increment()}>自增</button>
<button onClick={() => decrement()}>自減</button>
<button onClick={() => reset()}>重置</button>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
counter: state.couter,
};
};
const mapDispatchToProps = (dispatch) => {
return {
increment: () => {
dispatch(increment());
},
decrement: () => {
dispatch(decrement());
},
reset: () => {
dispatch(reset());
},
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
複製代碼
下面咱們要傳入store
全部容器組件均可以訪問 Redux store,因此能夠手動監聽它。一種方式是把它以 props 的形式傳入到全部容器組件中。但這太麻煩了,由於必需要用 store 把展現組件包裹一層,僅僅是由於剛好在組件樹中渲染了一個容器組件。
建議的方式是使用指定的 React Redux 組件 來 讓全部容器組件均可以訪問 store,而沒必要顯示地傳遞它。只須要在渲染根組件時使用便可。
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './css/index';
import { Provider } from 'react-redux';
import getRouter from './router/router';
import store from './redux/store';
const router = getRouter();
/* 初始化 */
renderWithHotReload(router);
function renderWithHotReload(RootElement) {
ReactDOM.render(
<Provider store={store}>{RootElement}</Provider>,
document.getElementById('root')
);
}
// 還須要在主要的js文件裏寫入下面這段代碼
if (module.hot) {
// 實現熱更新
module.hot.accept();
}
複製代碼
咱們在說清楚一下 1.Provider
組件是讓全部的組件能夠訪問到store。不用手動去傳。也不用手動去監聽。 2.connect
函數做用是從 Redux state 樹中讀取部分數據,並經過 props 來把這些數據提供給要渲染的組件。也傳遞dispatch(action)函數到props。
下面,咱們以向後臺請求用戶基本信息爲例。 1.咱們先建立一個user.json,等會請求用,至關於後臺的API接口。
在根目錄
mkdir api
cd api
touch user.json
複製代碼
user.json
{
"name": "xiehaitao",
"intro": "please give me a star"
}
複製代碼
2.建立必須的action建立函數。
cd src/redux/actions
touch userInfo.js
複製代碼
src/redux/actions/userInfo.js
export const GET_USER_INFO_REQUEST = "GET_USER_INFO_REQUEST";
export const GET_USER_INFO_SUCCESS = "GET_USER_INFO_SUCCESS";
export const GET_USER_INFO_FAIL = "GET_USER_INFO_FAIL";
function getUserInfoRequest() {
return {
type: GET_USER_INFO_REQUEST
}
}
function getUserInfoSuccess(userInfo) {
return {
type: GET_USER_INFO_SUCCESS,
userInfo: userInfo
}
}
function getUserInfoFail() {
return {
type: GET_USER_INFO_FAIL
}
}
複製代碼
咱們建立了請求中,請求成功,請求失敗三個action建立函數。 3.建立reducer
cd src/redux/reducers
touch userInfo.js
複製代碼
src/redux/reducers/userInfo.js
import { GET_USER_INFO_REQUEST, GET_USER_INFO_SUCCESS, GET_USER_INFO_FAIL } from 'actions/userInfo';
const initState = {
isLoading: false,
userInfo: {},
errorMsg: '',
};
export default function reducer(state = initState, action) {
switch (action.type) {
case GET_USER_INFO_REQUEST:
return {
...state,
isLoading: true,
userInfo: {},
errorMsg: '',
};
case GET_USER_INFO_SUCCESS:
return {
...state,
isLoading: false,
userInfo: action.userInfo,
errorMsg: '',
};
case GET_USER_INFO_FAIL:
return {
...state,
isLoading: false,
userInfo: {},
errorMsg: '請求錯誤',
};
default:
return state;
}
}
複製代碼
組合reducer src/redux/reducers.js
import counter from 'reducers/counter';
import userInfo from 'reducers/userInfo';
export default function combineReducers(state = {}, action) {
return {
couter: couter(state.couter, action),
userInfo: userInfo(state.userInfo, action)
}
}
複製代碼
4.如今有了action,有了reducer,咱們就須要調用把action裏面的三個action函數和網絡請求結合起來。 src/redux/actions/userInfo.js增長
export function getUserInfo() {
return function (dispatch) {
dispatch(getUserInfoRequest());
return fetch('/api/user.json')
.then((response) => {
return response.json();
})
.then((json) => {
dispatch(getUserInfoSuccess(json));
})
.catch(() => {
dispatch(getUserInfoFail());
});
};
}
複製代碼
咱們這裏發現,別的action建立函數都是返回action對象: 可是咱們如今的這個action建立函數 getUserInfo則是返回函數了。 爲了讓action建立函數除了返回action對象外,還能夠返回函數,咱們須要引用redux-thunk。
npm install --save redux-thunk
複製代碼
簡單的說,中間件就是action在到達reducer,先通過中間件處理。咱們以前知道reducer能處理的action只有這樣的{type:xxx},因此咱們使用中間件來處理 函數形式的action,把他們轉爲標準的action給reducer。這是redux-thunk的做用。 使用redux-thunk中間件 src/redux/store.js
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import combineReducers from './reducers';
const store = createStore(combineReducers, applyMiddleware(thunkMiddleware));
export default store;
複製代碼
到這裏,redux-thunk已經配置完成了,寫一個組件來驗證一下
cd src/pages
mkdir UserInfo
cd UserInfo
touch UserInfo.js
複製代碼
src/pages/UserInfo/UserInfo.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getUserInfo } from 'actions/userInfo';
class UserInfo extends Component {
render() {
const {
userInfo: { userInfo, isLoading, errorMsg },
getUserInfo,
} = this.props;
return (
<div>
{isLoading
? '請求信息中......'
: errorMsg || (
<div>
<p>用戶信息:</p>
<p>
用戶名:
{userInfo.name}
</p>
<p>
介紹:
{userInfo.intro}
</p>
</div>
)}
<button onClick={() => getUserInfo()}>請求用戶信息</button>
</div>
);
}
}
export default connect(
state => ({ userInfo: state.userInfo }),
{ getUserInfo }
)(UserInfo);
複製代碼
增長路由 src/router/router.js
import React from 'react';
import { HashRouter as Router, Route, Switch, Link } from 'react-router-dom';
import Home from 'pages/Home/Home';
import Page1 from 'pages/Page1/Page1';
import Counter from 'pages/Counter/Counter';
import UserInfo from 'pages/UserInfo/UserInfo';
const getRouter = () => (
<Router>
<div>
<ul>
<li>
<Link to="/">首頁</Link>
</li>
<li>
<Link to="/page1">Page1</Link>
</li>
<li>
<Link to="/couter">Counter</Link>
</li>
<li>
<Link to="/userinfo">UserInfo</Link>
</li>
</ul>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/page1" component={Page1} />
<Route path="/couter" component={Counter} />
<Route path="/userinfo" component={UserInfo} />
</Switch>
</div>
</Router>
);
export default getRouter;
複製代碼
redux提供了一個combineReducers函數來合併reducer,不用本身合併 src/redux/reducers.js
import { combineReducers } from 'redux';
import userInfo from 'reducers/userInfo';
import couter from './reducers/couters';
export default combineReducers({
couter,
userInfo,
});
複製代碼
在webpack4以前都是須要建三個文件來須要環境的,webpack.base.js,webpack.dev.js,webpack.prod.js,如今在webpack4以後就不須要了,由於用--mode 就能夠區分環境了 先安裝
npm install -D yargs-parser
複製代碼
這個包能夠拿到--mode 裏面的參數,這樣子就能夠區別是本地環境仍是線上環境了
"dev": "cross-env webpack-dev-server --mode development",
"build": "npm run lint && cross-env npm run clean && webpack --mode production",
複製代碼
// webpack.config.js
const argv = require('yargs-parser')(process.argv.slice(2));
const pro = argv.mode == 'production' ? true : false; // 區別是生產環境和開發環境
let plu = [];
if (pro) {
// 線上環境
plu.push(
new HtmlWebpackPlugin({
template: './src/index.html',
hash: true, // 會在打包好的bundle.js後面加上hash串
chunks: ['vendor', 'index', 'utils'] // 引入須要的chunk
}),
// 拆分後會把css文件放到dist目錄下的css/style.css
new ExtractTextWebpackPlugin('css/style.[chunkhash].css'),
new ExtractTextWebpackPlugin('css/reset.[chunkhash].css'),
new CleanWebpackPlugin('dist'),
)
} else {
// 開發環境
plu.push(
new HtmlWebpackPlugin({
template: './src/index.html',
chunks: ['vendor', 'index', 'utils'] // 引入須要的chunk
}),
// 拆分後會把css文件放到dist目錄下的css/style.css
new ExtractTextWebpackPlugin('css/style.css'),
new ExtractTextWebpackPlugin('css/reset.css'),
new webpack.HotModuleReplacementPlugin(), // 熱更新,熱更新不是刷新
)
}
devtool: pro ? '' : 'inline-source-map' // 只有本地開發才須要調試
複製代碼
eslint目標是以可擴展,每條規則獨立,不內置編碼風格爲理念的lint工具,用戶能夠定製本身的規則作成公共包
eslint主要有如下特色:
1)默認規則包含全部的jslint,jshint中存在的規則易遷移
2)規則可配置性高,可設置警告,錯誤兩個error等級,也能夠直接禁用
3)包含代碼風格檢測的規則
4)支持插件擴展,自定義規則
針對react開發者,eslint已經能夠很好的支持jsx語法了。 先安裝插件
npm install -D eslint eslint-config-airbnb eslint-loader eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react
複製代碼
配置.eslintrc文件
// 直接繼承airbnb的配置規則,同時也能夠寫入本身的特定規則,後面的內容會覆蓋默認的規則,
// 下面是我比較習慣的lint規則
{
"extends": ["airbnb"],
"env": {
"browser": true,
"node": true,
"es6": true,
"mocha": true,
"jest": true,
"jasmine": true
},
"rules": {
"no-plusplus": [0],
"eqeqeq": [0],
"no-empty": [0],
"no-param-reassign": [0],
"generator-star-spacing": [0],
"consistent-return": [0],
"no-shadow": [0],
"react/forbid-prop-types": [0],
"react/jsx-filename-extension": [
1,
{
"extensions": [".js"]
}
],
"react/button-has-type": [
"<enabled>",
{
"button": false,
"submit": false,
"reset": false
}
],
"global-require": [1],
"import/prefer-default-export": [0],
"react/jsx-boolean-value": [0],
"react/jsx-no-bind": [0],
"react/prop-types": [0],
"react/prefer-stateless-function": [0],
"react/jsx-one-expression-per-line": [0],
"react/jsx-wrap-multilines": [
"error",
{
"no-empty": [0],
"no-param-reassign": [0],
"declaration": "parens-new-line",
"assignment": "parens-new-line",
"return": "parens-new-line",
"arrow": "parens-new-line",
"condition": "parens-new-line",
"logical": "parens-new-line",
"prop": "ignore"
}
],
"no-else-return": [0],
"no-restricted-syntax": [0],
"import/no-extraneous-dependencies": [0],
"no-use-before-define": [0],
"jsx-a11y/no-static-element-interactions": [0],
"jsx-a11y/no-noninteractive-element-interactions": [0],
"jsx-a11y/click-events-have-key-events": [0],
"jsx-a11y/anchor-is-valid": [0],
"no-nested-ternary": [0],
"arrow-body-style": [0],
"import/extensions": [0],
"no-bitwise": [0],
"no-cond-assign": [0],
"import/no-unresolved": [0],
"comma-dangle": [
"error",
{
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "always-multiline",
"exports": "always-multiline",
"functions": "ignore"
}
],
"object-curly-newline": [0],
"function-paren-newline": [0],
"no-restricted-globals": [0],
"require-yield": [1]
},
"globals": {
"document": true,
"localStorage": true,
"window": true
}
}
複製代碼
除此以外還要在webpack.config.js文件增長module的loader
module: {
rules: [
{
enforce: "pre", // 表明在解析loader以前就先解析eslint-loader
test: /\.js$/,
exclude: /node_modules/,
include:/src/,
loader: "eslint-loader",
},
]
}
複製代碼
在pagekage.json
文件裏面script增長
"lint": "npm run format && npm run fix && eslint --ext .js src", // 檢測你寫的代碼是否符合eslint的規則
"fix": "npm run format && eslint --fix --ext .js src", // npm run fix 這個是能夠修復你沒有按照lint規則的寫法
複製代碼
###第一步 格式化全部代碼 prettier
npm install -D prettier
複製代碼
在package.json的script裏面添加以下配置
{
"scripts": {
"format": "prettier --single-quote --trailing-comma es5 --write \"src/**/*.js\""
}
}
複製代碼
你能夠經過 npm run format試一下是否能夠自動格式化你的代碼 第二步 配置Eslint 上面咱們已經配置好eslint了在package.json的scripts裏添加以下
"fix": "npm run format && eslint --fix --ext .js src",
複製代碼
第三步 添加Git鉤子(Pre-commit Hook)
Git 鉤子(hooks)是在Git 倉庫中特定事件(certain points)觸發後被調用的腳本。 詳情可瀏覽 git-scm.com/book/zh/v2/… 每次提交代碼,執行
git commit
以後進行自動格式化,免去每次人爲手動格式化,使遠程倉庫代碼保持風格統一。
npm install -D lint-staged husky
複製代碼
在package.json的scripts裏添加以下
"precommit": "npm run lint",
複製代碼
如今讓咱們來看看在package.json的scripts的全部配置吧
"scripts": {
"dev": "cross-env webpack-dev-server --mode development",
"build": "npm run lint && cross-env npm run clean && webpack --mode production",
"precommit": "npm run lint",
"clean": "cross-env rm -rf dist && mkdir dist",
"test": "mocha --compilers js:babel-register --require ./test/test_helper.js --recursive",
"test:watch": "npm run test --watch",
"lint": "npm run format && npm run fix && eslint --ext .js src",
"fix": "npm run format && eslint --fix --ext .js src",
"format": "prettier --single-quote --trailing-comma es5 --write \"src/**/*.js\""
},
複製代碼
好了,接下來還會繼續維護這個腳手架,把react的SSR服務端渲染的腳手架也搭建起來