源碼地址:https://github.com/brickspert/react-family 歡迎star
提問反饋:blogjavascript
原文地址:https://github.com/brickspert/blog/issues/1(github這裏我會不斷更新教程的)css
此處不更新,github上會一直更新html
當我第一次跟着項目作react
項目的時候,因爲半截加入的,對框架了解甚少,只能跟着別人的樣板寫。對整個框架沒有一點了解。java
作項目,老是要解決各類問題的,因此每一個地方都須要去了解,可是對整個框架沒有一個總體的瞭解,實在是不行。node
期間,我也跟着別人的搭建框架的教程一步一步的走,可是常常由於本身太菜,走不下去。在通過各類蹂躪以後,對整個框架也有一個大概的瞭解,
我就想把他寫下來,讓後來的菜鳥能跟着個人教程對react
全家桶有一個全面的認識。react
個人這個教程,重新建根文件夾開始,到成型的框架,每一個文件爲何要創建?創建了幹什麼?每一個依賴都是幹什麼的?一步一步寫下來,供你們學習。webpack
固然,這個框架我之後會一直維護的,也但願你們能一塊兒來完善這個框架,若是您有任何建議,歡迎留言,歡迎fork
。ios
在完善本框架的同時,我準備再新建一個兼容ie8
的框架react-family-ie8
,固然是基於該框架改造的。git
cd src/pages mkdir Home
│ .babelrc #babel配置文件 │ package-lock.json │ package.json │ README.MD │ webpack.config.js #webpack生產配置文件 │ webpack.dev.config.js #webpack開發配置文件 │ ├─dist ├─public #公共資源文件 └─src #項目源碼 │ index.html #index.html模板 │ index.js #入口文件 │ ├─component #組建庫 │ └─Hello │ Hello.js │ ├─pages #頁面目錄 │ ├─Counter │ │ Counter.js │ │ │ ├─Home │ │ Home.js │ │ │ ├─Page1 │ │ │ Page1.css #頁面樣式 │ │ │ Page1.js │ │ │ │ │ └─images #頁面圖片 │ │ brickpsert.jpg │ │ │ └─UserInfo │ UserInfo.js │ ├─redux │ │ reducers.js │ │ store.js │ │ │ ├─actions │ │ counter.js │ │ userInfo.js │ │ │ ├─middleware │ │ promiseMiddleware.js │ │ │ └─reducers │ counter.js │ userInfo.js │ └─router #路由文件 Bundle.js router.js
建立文件夾並進入es6
`mkdir react-family && cd react-family`
init npm
`npm init` 按照提示填寫項目基本信息
webpack
npm install --save-dev webpack
Q: 何時用--save-dev
,何時用--save
?
A: --save-dev
是你開發時候依賴的東西,--save
是你發佈以後還依賴的東西。看這裏
根據webpack文檔編寫最基礎的配置文件
新建webpack
開發配置文件 touch webpack.dev.config.js
webpack.dev.config.js
const path = require('path'); module.exports = { /*入口*/ entry: path.join(__dirname, 'src/index.js'), /*輸出到dist文件夾,輸出文件名字爲bundle.js*/ output: { path: path.join(__dirname, './dist'), filename: 'bundle.js' } };
webpack
編譯文件新建入口文件
mkdir src && touch ./src/index.js
src/index.js
添加內容
document.getElementById('app').innerHTML = "Webpack works"
如今咱們執行命令 webpack --config webpack.dev.config.js
咱們能夠看到生成了dist
文件夾和bundle.js
。
如今咱們測試下~
dist
文件夾下面新建一個index.html
touch ./dist/index.html
dist/index.html
填寫內容
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="app"></div> <script type="text/javascript" src="./bundle.js" charset="utf-8"></script> </body> </html>
用瀏覽器打開index.html
,能夠看到Webpack works
!
如今回頭看下,咱們作了什麼或者說webpack
作了什麼。
把入口文件 index.js
通過處理以後,生成 bundle.js
。就這麼簡單。
Babel 把用最新標準編寫的 JavaScript 代碼向下編譯成能夠在今天隨處可用的版本。 這一過程叫作「源碼到源碼」編譯, 也被稱爲轉換編譯。
通俗的說,就是咱們能夠用ES6, ES7等來編寫代碼,Babel會把他們通通轉爲ES5。
npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0
新建babel
配置文件.babelrc
touch .babelrc
.babelrc
{ "presets": [ "es2015", "react", "stage-0" ], "plugins": [] }
修改webpack.dev.config.js
,增長babel-loader
!
/*src文件夾下面的以.js結尾的文件,要使用babel解析*/ /*cacheDirectory是用來緩存編譯結果,下次編譯加速*/ module: { rules: [{ test: /\.js$/, use: ['babel-loader?cacheDirectory=true'], include: path.join(__dirname, 'src') }] }
如今咱們簡單測試下,是否能正確轉義ES6~
修改 src/index.js
/*使用es6的箭頭函數*/ var func = str => { document.getElementById('app').innerHTML = str; }; func('我如今在使用Babel!');
執行打包命令webpack --config webpack.dev.config.js
瀏覽器打開index.html
,咱們看到正確輸出了我如今在使用Babel!
而後咱們打開打包後的bundle.js
,翻頁到最下面,能夠看到箭頭函數被轉換成普通函數了!
Q: babel-preset-state-0
,babel-preset-state-1
,babel-preset-state-2
,babel-preset-state-3
有什麼區別?
A: 每一級包含上一級的功能,好比 state-0
包含state-1
的功能,以此類推。state-0
功能最全。具體能夠看這篇文章:babel配置-各階段的stage的區別
參考地址:
npm install --save react react-dom
修改 src/index.js
使用react
import React from 'react'; import ReactDom from 'react-dom'; ReactDom.render( <div>Hello React!</div>, document.getElementById('app'));
執行打包命令webpack --config webpack.dev.config.js
打開index.html
看效果。
咱們簡單作下改進,把Hello React
放到組件裏面。體現組件化~
cd src mkdir component cd component mkdir Hello cd Hello touch Hello.js
按照React語法,寫一個Hello組件
import React, {Component} from 'react'; export default class Hello extends Component { render() { return ( <div> Hello,React! </div> ) } }
而後讓咱們修改src/index.js
,引用Hello
組件!
src/index.js
import React from 'react'; import ReactDom from 'react-dom'; import Hello from './component/Hello/Hello'; ReactDom.render( <Hello/>, document.getElementById('app'));
在根目錄執行打包命令
webpack --config webpack.dev.config.js
打開index.html
看效果咯~
Q:每次打包都得在根目錄執行這麼一長串命令webpack --config webpack.dev.config.js
,能不打這麼長嗎?
A:修改package.json
裏面的script
,增長dev-build
。
package.json
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev-build": "webpack --config webpack.dev.config.js" }
如今咱們打包只須要執行npm start-build
就能夠啦!
參考地址:
http://www.ruanyifeng.com/blo...
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~ </div> ) } }
Page1.js
import React, {Component} from 'react'; export default class Page1 extends Component { render() { return ( <div> this is Page1~ </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('app'));
如今執行打包命令npm start-build
。打開index.html
查看效果啦!
那麼問題來了~咱們發現點擊‘首頁’和‘Page1’沒有反應。不要驚慌,這是正常的。
咱們以前一直用這個路徑訪問index.html
,相似這樣:file:///F:/react/react-family/dist/index.html
。
這種路徑了,不是咱們想象中的路由那樣的路徑http://localhost:3000
~咱們須要配置一個簡單的WEB服務器,指向index.html
~有下面兩種方法來實現
Nginx
, Apache
, IIS
等配置啓動一個簡單的的WEB服務器。webpack-dev-server
來配置啓動WEB服務器。下一節,咱們來使用第二種方法啓動服務器。這一節的DEMO,先放這裏。
參考地址
簡單來講,webpack-dev-server
就是一個小型的靜態文件服務器。使用它,能夠爲webpack
打包生成的資源文件提供Web服務。
npm install webpack-dev-server --save-dev
修改webpack.dev.config.js
,增長webpack-dev-server
的配置。
webpack.dev.config.js
devServer: { contentBase: path.join(__dirname, './dist') }
如今執行
webpack-dev-server --config webpack.dev.config.js
瀏覽器打開http://localhost:8080,OK,如今咱們能夠點擊首頁
,Page1
了,
看URL地址變化啦!咱們看到react-router
已經成功了哦。
Q: --content-base
是什麼?
A:URL的根目錄。若是不設定的話,默認指向項目根目錄。
**重要提示:webpack-dev-server編譯後的文件,都存儲在內存中,咱們並不能看見的。你能夠刪除以前遺留的文件dist/bundle.js
,
仍然能正常打開網站!**
每次執行webpack-dev-server --config webpack.dev.config.js
,要打很長的命令,咱們修改package.json
,增長script->start
:
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev-build": "webpack --config webpack.dev.config.js", "start": "webpack-dev-server --config webpack.dev.config.js" }
下次執行npm start
就能夠了。
既然用到了webpack-dev-server
,咱們就看看它的其餘的配置項。
看了以後,發現有幾個咱們能夠用的。
console
中打印彩色日誌404
響應都被替代爲index.html
。有什麼用呢?你如今運行npm start
,而後打開瀏覽器,訪問http://localhost:8080
,而後點擊Page1
到連接http://localhost:8080/page1
,而後刷新頁面試試。是否是發現刷新後404
了。爲何?dist
文件夾裏面並無page1.html
,固然會404
了,因此咱們須要配置historyApiFallback
,讓全部的404
定位到index.html
。
host
,默認是localhost
。若是你但願服務器外部能夠訪問,指定以下:host: "0.0.0.0"
。好比你用手機經過IP訪問。Webpack
的模塊熱替換特性。關於熱模塊替換,我下一小節專門講解一下。8080
端口。localhost:3000
上有後端服務的話,你能夠這樣啓用代理:proxy: { "/api": "http://localhost:3000" }
根據這幾個配置,修改下咱們的webpack-dev-server
的配置~
webpack.dev.config.js
devServer: { contentBase: path.join(__dirname, './dist'), historyApiFallback: true, host: '0.0.0.0' }
CLI ONLY
的須要在命令行中配置
package.json
"dev": "webpack-dev-server --config webpack.dev.config.js --color --progress"
如今咱們執行npm start
看看效果。是否是看到打包的時候有百分比進度?在http://localhost:8080/page1
頁面刷新是否是沒問題了?
用手機經過局域網IP是否能夠訪問到網站?
參考地址:
到目前,當咱們修改代碼的時候,瀏覽器會自動刷新,不信你能夠去試試。(若是你的不會刷新,看看這個調整文本編輯器)
我相信看這個教程的人,應該用過別人的框架。咱們在修改代碼的時候,瀏覽器不會刷新,只會更新本身修改的那一塊。咱們也要實現這個效果。
咱們看下webpack模塊熱替換教程。
咱們接下來要這麼修改
package.json
增長 --hot
"dev": "webpack-dev-server --config webpack.dev.config.js --color --progress --hot"
src/index.js
增長module.hot.accept()
,以下。當模塊更新的時候,通知index.js
。
src/index.js
import React from 'react'; import ReactDom from 'react-dom'; import getRouter from './router/router'; if (module.hot) { module.hot.accept(); } ReactDom.render( getRouter(), document.getElementById('app'));
如今咱們執行npm start
,打開瀏覽器,修改Home.js
,看是否是不刷新頁面的狀況下,內容更新了?驚不驚喜?意不意外?
作模塊熱替換,咱們只改了幾行代碼,很是簡單的。紙老虎一個~
如今我須要說明下咱們命令行使用的--hot
,能夠經過配置webpack.dev.config.js
來替換,
向文檔上那樣,修改下面三處。但咱們仍是用--hot
吧。下面的方式咱們知道一下就行,咱們不用。一樣的效果。
const webpack = require('webpack'); devServer: { hot: true } plugins:[ new webpack.HotModuleReplacementPlugin() ]
HRM
配置其實有兩種方式,一種CLI
方式,一種Node.js API
方式。咱們用到的就是CLI
方式,比較簡單。Node.js API
方式,就是建一個server.js
等等,網上大部分教程都是這種方式,這裏不作講解了。
你覺得模塊熱替換到這裏就結束了?no~no~no~
上面的配置對react
模塊的支持不是很好哦。
例以下面的demo
,當模塊熱替換的時候,state
會重置,這不是咱們想要的。
修改Home.js
,增長計數state
src/pages/Home/Home.js
import React, {Component} from 'react'; export default class Home extends Component { constructor(props) { super(props); this.state = { count: 0 } } _handleClick() { this.setState({ count: ++this.state.count }); } render() { return ( <div> this is home~<br/> 當前計數:{this.state.count}<br/> <button onClick={() => this._handleClick()}>自增</button> </div> ) } }
你能夠測試一下,當咱們修改代碼的時候,webpack
在更新頁面的時候,也把count
初始爲0了。
爲了在react
模塊更新的同時,能保留state
等頁面中其餘狀態,咱們須要引入react-hot-loader~
Q: 請問webpack-dev-server
與react-hot-loader
二者的熱替換有什麼區別?
A: 區別在於webpack-dev-serve
r本身的--hot
模式只能即時刷新頁面,但狀態保存不住。由於React
有一些本身語法(JSX)是HotModuleReplacementPlugin
搞不定的。
而react-hot-loader
在--hot
基礎上作了額外的處理,來保證狀態能夠存下來。(來自segmentfault)
下面咱們來加入react-hot-loader v3
,
安裝依賴
npm install react-hot-loader@next --save-dev
根據文檔,
咱們要作以下幾個修改~
.babelrc
增長 react-hot-loader/babel
.babelrc
{ "presets": [ "es2015", "react", "stage-0" ], "plugins": [ "react-hot-loader/babel" ] }
webpack.dev.config.js
入口增長react-hot-loader/patch
webpack.dev.config.js
entry: [ 'react-hot-loader/patch', path.join(__dirname, 'src/index.js') ]
src/index.js
修改以下src/index.js
import React from 'react'; import ReactDom from 'react-dom'; import {AppContainer} from 'react-hot-loader'; import getRouter from './router/router'; /*初始化*/ renderWithHotReload(getRouter()); /*熱更新*/ if (module.hot) { module.hot.accept('./router/router', () => { const getRouter = require('./router/router').default; renderWithHotReload(getRouter()); }); } function renderWithHotReload(RootElement) { ReactDom.render( <AppContainer> {RootElement} </AppContainer>, document.getElementById('app') ) }
如今,執行npm start
,試試。是否是修改頁面的時候,state
不更新了?
參考文章:
作到這裏,咱們簡單休息下。作下優化~
在以前寫的代碼中,咱們引用組件,或者頁面時候,寫的是相對路徑~
好比src/router/router.js
裏面,引用Home.js
的時候就用的相對路徑
import Home from '../pages/Home/Home';
webpack提供了一個別名配置,就是咱們不管在哪一個路徑下,引用均可以這樣
import Home from 'pages/Home/Home';
下面咱們來配置下,修改webpack.dev.config.js
,增長別名~
webpack.config.js
resolve: { alias: { pages: path.join(__dirname, 'src/pages'), component: path.join(__dirname, 'src/component'), router: path.join(__dirname, 'src/router') } }
而後咱們把以前使用的絕對路徑通通改掉。
src/router/router.js
import Home from 'pages/Home/Home'; import Page1 from 'pages/Page1/Page1';
src/index.js
import getRouter from 'router/router';
咱們這裏約定,下面,咱們會默認配置須要的別名路徑,再也不作重複的講述哦。
接下來,咱們就要就要就要集成redux
了。
要對redux
有一個大概的認識,能夠閱讀阮一峯前輩的Redux 入門教程(一):基本用法
若是要對redux
有一個很是詳細的認識,我推薦閱讀中文文檔,寫的很是好。讀了這個教程,有一個很是深入的感受,redux
並無任何魔法。
不要被各類關於 reducers, middleware, store 的演講所矇蔽 ---- Redux 實際是很是簡單的。
固然,我這篇文章是寫給新手的,若是看不懂上面的文章,或者不想看,不要緊。先會用,多用用就知道原理了。
開始整代碼!咱們就作一個最簡單的計數器。自增,自減,重置。
先安裝redux
npm install --save redux
初始化目錄結構
cd src mkdir redux cd redux mkdir actions mkdir reducers touch reducers.js touch store.js touch actions/counter.js touch reducers/counter.js
先來寫action
建立函數。經過action
建立函數,能夠建立action
~src/redux/actions/counter.js
/*action*/ export const INCREMENT = "counter/INCREMENT"; export const DECREMENT = "counter/DECREMENT"; export const RESET = "counter/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/counter.js
import {INCREMENT, DECREMENT, RESET} from '../actions/counter'; /* * 初始化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/counter'; 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;
到如今爲止,咱們已經可使用redux
了~
下面咱們就簡單的測試下
cd src cd redux touch testRedux.js
src/redux/testRedux.js
import {increment, decrement, reset} from './actions/counter'; import store from './store'; // 打印初始狀態 console.log(store.getState()); // 每次 state 更新時,打印日誌 // 注意 subscribe() 返回一個函數用來註銷監聽器 let unsubscribe = store.subscribe(() => console.log(store.getState()) ); // 發起一系列 action store.dispatch(increment()); store.dispatch(decrement()); store.dispatch(reset()); // 中止監聽 state 更新 unsubscribe();
當前文件夾執行命令
webpack testRedux.js build.js node build.js
是否是看到輸出了state
變化?
{ counter: { count: 0 } } { counter: { count: 1 } } { counter: { count: 0 } } { counter: { count: 0 } }
作這個測試,就是爲了告訴你們,redux
和react
不要緊,雖然說他倆能合做。
到這裏,我建議你再理下redux
的數據流,看看這裏。
store.dispatch(action)
提交action
。redux store
調用傳入的reducer
函數。把當前的state
和action
傳進去。reducer
應該把多個子 reducer
輸出合併成一個單一的 state
樹。Redux store
保存了根 reducer
返回的完整 state
樹。就是醬紫~~
這會webpack.dev.config.js
路徑別名增長一下,後面好寫了。
webpack.config.js
alias: { ... actions: path.join(__dirname, 'src/redux/actions'), reducers: path.join(__dirname, 'src/redux/reducers'), redux: path.join(__dirname, 'src/redux') }
把前面的相對路徑都改改。
下面咱們開始搭配react
使用。
寫一個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 {BrowserRouter 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="/counter">Counter</Link></li> </ul> <Switch> <Route exact path="/" component={Home}/> <Route path="/page1" component={Page1}/> <Route path="/counter" component={Counter}/> </Switch> </div> </Router> ); export default getRouter;
npm start
看看效果。
下一步,咱們讓Counter
組件和Redux
聯合起來。使Counter
能得到到Redux
的state
,而且能發射action
。
固然咱們可使用剛纔測試testRedux
的方法,手動監聽~手動引入store
~可是這確定很麻煩哦。
react-redux
提供了一個方法connect
。
容器組件就是使用 store.subscribe() 從 Redux state 樹中讀取部分數據,並經過 props 來把這些數據提供給要渲染的組件。你能夠手工來開發容器組件,但建議使用 React Redux 庫的 connect() 方法來生成,這個方法作了性能優化來避免不少沒必要要的重複渲染。
connect
接收兩個參數,一個mapStateToProps
,就是把redux
的state
,轉爲組件的Props
,還有一個參數是mapDispatchToprops
,
就是把發射actions
的方法,轉爲Props
屬性函數。
先來安裝react-redux
npm install --save react-redux
src/pages/Counter/Counter.js
import React, {Component} from 'react'; import {increment, decrement, reset} from 'actions/counter'; import {connect} from 'react-redux'; class Counter extends Component { render() { return ( <div> <div>當前計數爲{this.props.counter.count}</div> <button onClick={() => this.props.increment()}>自增 </button> <button onClick={() => this.props.decrement()}>自減 </button> <button onClick={() => this.props.reset()}>重置 </button> </div> ) } } const mapStateToProps = (state) => { return { counter: state.counter } }; 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 組件 <Provider> 來 魔法般的 讓全部容器組件均可以訪問 store,而沒必要顯示地傳遞它。只須要在渲染根組件時使用便可。
src/index.js
import React from 'react'; import ReactDom from 'react-dom'; import {AppContainer} from 'react-hot-loader'; import {Provider} from 'react-redux'; import store from './redux/store'; import getRouter from 'router/router'; /*初始化*/ renderWithHotReload(getRouter()); /*熱更新*/ if (module.hot) { module.hot.accept('./router/router', () => { const getRouter = require('router/router').default; renderWithHotReload(getRouter()); }); } function renderWithHotReload(RootElement) { ReactDom.render( <AppContainer> <Provider store={store}> {RootElement} </Provider> </AppContainer>, document.getElementById('app') ) }
到這裏咱們就能夠執行npm start
,打開localhost:8080/counter看效果了。
可是你發現npm start
一直報錯
ERROR in ./node_modules/react-redux/es/connect/mapDispatchToProps.js Module not found: Error: Can't resolve 'redux' in 'F:\Project\react\react-family\node_modules\react-redux\es\connect' ERROR in ./src/redux/store.js Module not found: Error: Can't resolve 'redux' in 'F:\Project\react\react-family\src\redux'
WTF?這個錯誤困擾了半天。我說下爲何形成這個錯誤。咱們引用redux
的時候這樣用的
import {createStore} from 'redux'
然而,咱們在webapck.dev.config.js
裏面這樣配置了
resolve: { alias: { ... redux: path.join(__dirname, 'src/redux') } }
而後webapck
編譯的時候碰到redux
都去src/redux
去找了。可是找不到啊。因此咱們把webpack.dev.config.js
裏面redux
這一行刪除了,就行了。
而且把使用咱們本身使用redux
文件夾的地方改爲相對路徑哦。
如今你能夠npm start
去看效果了。
這裏咱們再縷下(能夠讀React 實踐心得:react-redux 之 connect 方法詳解)
Provider
組件是讓全部的組件能夠訪問到store
。不用手動去傳。也不用手動去監聽。connect
函數做用是從 Redux state
樹中讀取部分數據,並經過 props 來把這些數據提供給要渲染的組件。也傳遞dispatch(action)
函數到props
。接下來,咱們要說異步action
參考地址: http://cn.redux.js.org/docs/a...
想象一下咱們調用一個異步get
請求去後臺請求數據:
isLoading
置爲true
。isLoading
置爲false
,data
填充數據。isLoading
置爲false
,顯示錯誤信息。下面,咱們以向後臺請求用戶基本信息爲例。
user.json
,等會請求用,至關於後臺的API接口。cd dist mkdir api cd api touch user.json
dist/api/user.json
{ "name": "brickspert", "intro": "please give me a star" }
action
建立函數。cd src/redux/actions touch userInfo.js
src/redux/actions/getUserInfo.js
export const GET_USER_INFO_REQUEST = "userInfo/GET_USER_INFO_REQUEST"; export const GET_USER_INFO_SUCCESS = "userInfo/GET_USER_INFO_SUCCESS"; export const GET_USER_INFO_FAIL = "userInfo/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
建立函數。
reducer
再強調下,reducer
是根據state
和action
生成新state
的純函數。
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; } }
這裏的...state
語法,是和別人的Object.assign()
起同一個做用,合併新舊state。咱們這裏是沒效果的,可是我建議都寫上這個哦
組合reducer
src/redux/reducers.js
import counter from 'reducers/counter'; import userInfo from 'reducers/userInfo'; export default function combineReducers(state = {}, action) { return { counter: counter(state.counter, action), userInfo: userInfo(state.userInfo, action) } }
如今有了action
,有了reducer
,咱們就須要調用把action
裏面的三個action
函數和網絡請求結合起來。
dispatch getUserInfoRequest
dispatch getUserInfoSuccess
dispatch getUserInfoFail
src/redux/actions/userInfo.js
增長
export function getUserInfo() { return function (dispatch) { dispatch(getUserInfoRequest()); return fetch('http://localhost:8080/api/user.json') .then((response => { return response.json() })) .then((json) => { dispatch(getUserInfoSuccess(json)) } ).catch( () => { dispatch(getUserInfoFail()); } ) } }
咱們這裏發現,別的action
建立函數都是返回action
對象:
{type: xxxx}
可是咱們如今的這個action
建立函數 getUserInfo
則是返回函數了。
爲了讓action
建立函數除了返回action
對象外,還能夠返回函數,咱們須要引用redux-thunk
。
npm install --save redux-thunk
這裏涉及到redux
中間件middleware
,我後面會講到的。你也能夠讀這裏Middleware。
簡單的說,中間件就是action
在到達reducer
,先通過中間件處理。咱們以前知道reducer
能處理的action
只有這樣的{type:xxx}
,因此咱們使用中間件來處理
函數形式的action
,把他們轉爲標準的action
給reducer
。這是redux-thunk
的做用。
使用redux-thunk
中間件
咱們來引入redux-thunk
中間件
src/redux/store.js
import {createStore, applyMiddleware} from 'redux'; import thunkMiddleware from 'redux-thunk'; import combineReducers from './reducers.js'; let store = createStore(combineReducers, applyMiddleware(thunkMiddleware)); export default store;
到這裏,redux
這邊OK了,咱們來寫個組件驗證下。
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, isLoading, errorMsg} = this.props.userInfo; return ( <div> { isLoading ? '請求信息中......' : ( errorMsg ? errorMsg : <div> <p>用戶信息:</p> <p>用戶名:{userInfo.name}</p> <p>介紹:{userInfo.intro}</p> </div> ) } <button onClick={() => this.props.getUserInfo()}>請求用戶信息</button> </div> ) } } export default connect((state) => ({userInfo: state.userInfo}), {getUserInfo})(UserInfo);
這裏你可能發現connect
參數寫法不同了,mapStateToProps
函數用了es6
簡寫,mapDispatchToProps
用了react-redux
提供的簡單寫法。
增長路由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'; 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="/counter">Counter</Link></li> <li><Link to="/userinfo">UserInfo</Link></li> </ul> <Switch> <Route exact path="/" component={Home}/> <Route path="/page1" component={Page1}/> <Route path="/counter" component={Counter}/> <Route path="/userinfo" component={UserInfo}/> </Switch> </div> </Router> ); export default getRouter;
如今你能夠執行npm start
去看效果啦!
到這裏redux
集成基本告一段落了,後面咱們還會有一些優化。
redux
提供了一個combineReducers
函數來合併reducer
,不用咱們本身合併哦。寫起來簡單,可是意思和咱們
本身寫的combinReducers
也是同樣的。
src/redux/reducers.js
import {combineReducers} from "redux"; import counter from 'reducers/counter'; import userInfo from 'reducers/userInfo'; export default combineReducers({ counter, userInfo });
如今咱們發現一個問題,代碼哪裏寫錯了,瀏覽器報錯只報在build.js
第幾行。
這讓咱們分析錯誤無從下手。看這裏。
咱們增長webpack
配置devtool
!
src/webpack.dev.config.js
增長
devtool: 'inline-source-map'
此次看錯誤信息是否是提示的很詳細了?
同時,咱們在srouce
裏面能看到咱們寫的代碼,也能打斷點調試哦~
先說這裏爲何不用scss
,由於Windows
使用node-sass
,須要先安裝 Microsoft Windows SDK for Windows 7 and .NET Framework 4。
我怕有些人copy這份代碼後,沒注意,運行不起來。因此這裏不用scss
了,若是須要,自行編譯哦。
npm install css-loader style-loader --save-dev
css-loader
使你可以使用相似@import
和 url(...)
的方法實現 require()
的功能;
style-loader
將全部的計算後的樣式加入頁面中; 兩者組合在一塊兒使你可以把樣式表嵌入webpack
打包後的JS文件中。
webpack.dev.config.js
rules
增長
{ test: /\.css$/, use: ['style-loader', 'css-loader'] }
咱們用Page1
頁面來測試下
cd src/pages/Page1 touch Page1.css
src/pages/Page1/Page1.css
.page-box { border: 1px solid red; }
src/pages/Page1/Page1.js
import React, {Component} from 'react'; import './Page1.css'; export default class Page1 extends Component { render() { return ( <div className="page-box"> this is page1~ </div> ) } }
好了,如今npm start
去看效果吧。
npm install --save-dev url-loader file-loader
webpack.dev.config.js
rules
增長
{ test: /\.(png|jpg|gif)$/, use: [{ loader: 'url-loader', options: { limit: 8192 } }] }
options limit 8192
意思是,小於等於8K的圖片會被轉成base64
編碼,直接插入HTML中,減小HTTP
請求。
咱們來用Page1
測試下
cd src/pages/Page1 mkdir images
給images
文件夾放一個圖片。
修改代碼,引用圖片
src/pages/Page1/Page1.js
import React, {Component} from 'react'; import './Page1.css'; import image from './images/brickpsert.jpg'; export default class Page1 extends Component { render() { return ( <div className="page-box"> this is page1~ <img src={image}/> </div> ) } }
能夠去看看效果啦。
爲何要實現按需加載?
咱們如今看到,打包完後,全部頁面只生成了一個build.js
,當咱們首屏加載的時候,就會很慢。由於他也下載了別的頁面的js
了哦。
若是每一個頁面都打包了本身單獨的JS,在進入本身頁面的時候才加載對應的js,那首屏加載就會快不少哦。
在 react-router 2.0
時代, 按需加載須要用到的最關鍵的一個函數,就是require.ensure()
,它是按需加載可以實現的核心。
在4.0版本,官方放棄了這種處理按需加載的方式,選擇了一個更加簡潔的處理方式。
根據官方示例,咱們開搞
npm install bundle-loader --save-dev
bundle.js
cd src/router touch Bundle.js
src/router/Bundle.js
import React, {Component} from 'react' class Bundle extends Component { state = { // short for "module" but that's a keyword in js, so "mod" mod: null }; componentWillMount() { this.load(this.props) } componentWillReceiveProps(nextProps) { if (nextProps.load !== this.props.load) { this.load(nextProps) } } load(props) { this.setState({ mod: null }); props.load((mod) => { this.setState({ // handle both es imports and cjs mod: mod.default ? mod.default : mod }) }) } render() { return this.props.children(this.state.mod) } } export default Bundle;
src/router/router.js
import React from 'react'; import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom'; import Bundle from './Bundle'; import Home from 'bundle-loader?lazy&name=home!pages/Home/Home'; import Page1 from 'bundle-loader?lazy&name=page1!pages/Page1/Page1'; import Counter from 'bundle-loader?lazy&name=counter!pages/Counter/Counter'; import UserInfo from 'bundle-loader?lazy&name=userInfo!pages/UserInfo/UserInfo'; const Loading = function () { return <div>Loading...</div> }; const createComponent = (component) => () => ( <Bundle load={component}> { (Component) => Component ? <Component/> : <Loading/> } </Bundle> ); const getRouter = () => ( <Router> <div> <ul> <li><Link to="/">首頁</Link></li> <li><Link to="/page1">Page1</Link></li> <li><Link to="/counter">Counter</Link></li> <li><Link to="/userinfo">UserInfo</Link></li> </ul> <Switch> <Route exact path="/" component={createComponent(Home)}/> <Route path="/page1" component={createComponent(Page1)}/> <Route path="/counter" component={createComponent(Counter)}/> <Route path="/userinfo" component={createComponent(UserInfo)}/> </Switch> </div> </Router> ); export default getRouter;
如今你能夠npm start
,打開瀏覽器,看是否是進入新的頁面,都會加載本身的JS的~
可是你可能發現,名字都是0.bundle.js
這樣子的,這分不清楚是哪一個頁面的js
呀!
咱們修改下webpack.dev.config.js
,加個chunkFilename
。chunkFilename
是除了entry
定義的入口js
以外的js
~
output: { path: path.join(__dirname, './dist'), filename: 'bundle.js', chunkFilename: '[name].js' }
如今你運行發現名字變成home.js
,這樣的了。棒棒噠!
那麼問題來了home
是在哪裏設置的?webpack
怎麼知道他叫home
?
其實在這裏咱們定義了,router.js
裏面
import Home from 'bundle-loader?lazy&name=home!pages/Home/Home';
看到沒。這裏有個name=home
。嘿嘿。
參考地址:
想象一下這個場景~
咱們網站上線了,用戶第一次訪問首頁,下載了home.js
,第二次訪問又下載了home.js
~
這確定不行呀,因此咱們通常都會作一個緩存,用戶下載一次home.js
後,第二次就不下載了。
有一天,咱們更新了home.js
,可是用戶不知道呀,用戶仍是使用本地舊的home.js
。出問題了~
怎麼解決?每次代碼更新後,打包生成的名字不同。好比第一次叫home.a.js
,第二次叫home.b.js
。
文檔看這裏
咱們照着文檔來
webpack.dev.config.js
output: { path: path.join(__dirname, './dist'), filename: '[name].[hash].js', chunkFilename: '[name].[chunkhash].js' }
每次打包都用增長hash
~
如今咱們試試,是否是修改了文件,打包後相應的文件名字就變啦?
可是你可能發現了,網頁打開報錯了~由於你dist/index.html
裏面引用js
名字仍是bundle.js
老名字啊,改爲新的名字就能夠啦。
啊~那豈不是我每次編譯打包,都得去改一下js名字?欲知後事如何,且看下節分享。
這個插件,每次會自動把js插入到你的模板index.html
裏面去。
npm install html-webpack-plugin --save-dev
新建模板index.html
cd src touch index.html
src/index.html
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="app"></div> </body> </html>
修改webpack.dev.config.js
,增長plugin
var HtmlWebpackPlugin = require('html-webpack-plugin'); plugins: [new HtmlWebpackPlugin({ filename: 'index.html', template: path.join(__dirname, 'src/index.html') })],
npm start
運行項目,看看是否是能正常訪問啦。~
說明一下:npm start
打包後的文件存在內存中,你看不到的。~ 你能夠把遺留dist/index.html
刪除掉了。
想象一下,咱們的主文件,原來的bundle.js
裏面是否是包含了react
,redux
,react-router
等等
這些代碼??這些代碼基本上不會改變的。可是,他們合併在bundle.js
裏面,每次項目發佈,從新請求bundle.js
的時候,至關於從新請求了react
等這些公共庫。浪費了~
咱們把react
這些不會改變的公共庫提取出來,用戶緩存下來。今後之後,用戶不再用下載這些庫了,不管是否發佈項目。嘻嘻。
webpack
文檔給了教程,看這裏
webpack.dev.config.js
var webpack = require('webpack'); entry: { app: [ 'react-hot-loader/patch', path.join(__dirname, 'src/index.js') ], vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux'] } /*plugins*/ new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' })
把react
等庫生成打包到vendor.hash.js
裏面去。
可是你如今可能發現編譯生成的文件app.[hash].js
和vendor.[hash].js
生成的hash
同樣的,這裏是個問題,由於呀,你每次修改代碼,都會致使vendor.[hash].js
名字改變,那咱們提取出來的意義也就沒了。其實文檔上寫的很清楚,
output: { path: path.join(__dirname, './dist'), filename: '[name].[hash].js', //這裏應該用chunkhash替換hash chunkFilename: '[name].[chunkhash].js' }
可是無奈,若是用chunkhash
,會報錯。和webpack-dev-server --hot
不兼容,具體看這裏。
如今咱們在配置開發版配置文件,就向webpack-dev-server
妥協,由於咱們要用他。問題先放這裏,等會咱們配置正式版webpack.config.js
的時候要解決這個問題。
開發環境(development)和生產環境(production)的構建目標差別很大。在開發環境中,咱們須要具備強大的、具備實時從新加載(live reloading)或熱模塊替換(hot module replacement)能力的 source map 和 localhost server。而在生產環境中,咱們的目標則轉向於關注更小的 bundle,更輕量的 source map,以及更優化的資源,以改善加載時間。因爲要遵循邏輯分離,咱們一般建議爲每一個環境編寫彼此獨立的 webpack 配置。
文檔看這裏
咱們要開始作了~
touch webpack.config.js
在webpack.dev.config.js
的基礎上先作如下幾個修改~
webpack-dev-server
相關的東西~devtool
的值改爲cheap-module-source-map
hash
改爲chunkhash
webpack.config.js
const path = require('path'); var HtmlWebpackPlugin = require('html-webpack-plugin'); var webpack = require('webpack'); module.exports = { devtool: 'cheap-module-source-map', entry: { app: [ path.join(__dirname, 'src/index.js') ], vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux'] }, output: { path: path.join(__dirname, './dist'), filename: '[name].[chunkhash].js', chunkFilename: '[name].[chunkhash].js' }, module: { rules: [{ test: /\.js$/, use: ['babel-loader'], include: path.join(__dirname, 'src') }, { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.(png|jpg|gif)$/, use: [{ loader: 'url-loader', options: { limit: 8192 } }] }] }, plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', template: path.join(__dirname, 'src/index.html') }), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }) ], resolve: { alias: { pages: path.join(__dirname, 'src/pages'), component: path.join(__dirname, 'src/component'), router: path.join(__dirname, 'src/router'), actions: path.join(__dirname, 'src/redux/actions'), reducers: path.join(__dirname, 'src/redux/reducers') } } };
在package.json
增長打包腳本
"build":"webpack --config webpack.config.js"
而後執行npm run build
~看看dist
文件夾是否是生成了咱們發佈要用的全部文件哦?
接下來咱們仍是要優化正式版配置文件~
webpack
使用UglifyJSPlugin
來壓縮生成的文件。
npm i --save-dev uglifyjs-webpack-plugin
webpack.config.js
const UglifyJSPlugin = require('uglifyjs-webpack-plugin') module.exports = { plugins: [ new UglifyJSPlugin() ] }
npm run build
發現打包文件大小減少了好多。
許多 library 將經過與 process.env.NODE_ENV 環境變量關聯,以決定 library 中應該引用哪些內容。例如,當不處於生產環境中時,某些 library 爲了使調試變得容易,可能會添加額外的日誌記錄(log)和測試(test)。其實,當使用 process.env.NODE_ENV === 'production' 時,一些 library 可能針對具體用戶的環境進行代碼優化,從而刪除或添加一些重要代碼。咱們可使用 webpack 內置的 DefinePlugin 爲全部的依賴定義這個變量:
webpack.config.js
module.exports = { plugins: [ new webpack.DefinePlugin({ 'process.env': { 'NODE_ENV': JSON.stringify('production') } }) ] }
npm run build
後發現vendor.[hash].js
又變小了。
剛纔咱們把[name].[hash].js
變成[name].[chunkhash].js
後,npm run build
後,
發現app.xxx.js
和vendor.xxx.js
不同了哦。
可是如今又有一個問題了。
你隨便修改代碼一處,例如Home.js
,隨便改變個字,你發現home.xxx.js
名字變化的同時,vendor.xxx.js
名字也變了。這不行啊。這和沒拆分不是同樣同樣了嗎?咱們本意是vendor.xxx.js
名字永久不變,一直緩存在用戶本地的。~
官方文檔推薦了一個插件HashedModuleIdsPlugin
plugins: [ new webpack.HashedModuleIdsPlugin() ]
如今你打包,修改代碼再試試,是否是名字不變啦?錯了,如今打包,我發現名字仍是變了,通過比對文檔,我發現還要加一個runtime
代碼抽取,
new webpack.optimize.CommonsChunkPlugin({ name: 'runtime' })
加上這句話就行了~爲何呢?看下解釋。
注意,引入順序在這裏很重要。CommonsChunkPlugin 的 'vendor' 實例,必須在 'runtime' 實例以前引入。
想象一個場景,咱們的靜態文件放在了單獨的靜態服務器上去了,那咱們打包的時候,如何讓靜態文件的連接定位到靜態服務器呢?
看文檔Public Path
webpack.config.js
output
中增長一個publicPath
,咱們當前用/
,相對於當前路徑,若是你要改爲別的url
,就改這裏就行了。
output: { publicPath : '/' }
你如今打開dist
,是否是發現好多好多文件,每次打包後的文件在這裏混合了?咱們但願每次打包前自動清理下dist
文件。
npm install clean-webpack-plugin --save-dev
webpack.config.js
const CleanWebpackPlugin = require('clean-webpack-plugin'); plugins: [ new CleanWebpackPlugin(['dist']) ]
如今npm run bundle
試試,是否是以前的都清空了。固然咱們以前的api
文件夾也被清空了,不過不要緊哦~原本就是測試用的。
目前咱們的css
是直接打包進js
裏面的,咱們但願能單獨生成css
文件。
咱們使用extract-text-webpack-plugin來實現。
npm install --save-dev extract-text-webpack-plugin
webpack.config.js
const ExtractTextPlugin = require("extract-text-webpack-plugin"); module.exports = { module: { rules: [ { test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: "css-loader" }) } ] }, plugins: [ new ExtractTextPlugin({ filename: '[name].[contenthash:5].css', allChunks: true }) ] }
npm run build
後發現單獨生成了css
文件哦
axios
和middleware
優化API請求先安裝下axios
npm install --save axios
咱們以前項目的一次API請求是這樣寫的哦~
action
建立函數是這樣的。比咱們如今寫的fetch
簡單多了。
export function getUserInfo() { return { types: [GET_USER_INFO_REQUEST, GET_USER_INFO_SUCCESS, GET_USER_INFO_FAIL], promise: client => client.get(`http://localhost:8080/api/user.json`) afterSuccess:(dispatch,getState,response)=>{ /*請求成功後執行的函數*/ }, otherData:otherData } }
而後在dispatch(getUserInfo())後,經過redux
中間件來處理請求邏輯。
中間件的教程看這裏
咱們想一想中間件的邏輯
dispatch
REQUEST
請求。dispatch
SUCCESS
請求,若是定義了afterSuccess()
函數,調用它。dispatch
FAIL
請求。來寫一個
cd src/redux mkdir middleware cd middleware touch promiseMiddleware.js
src/redux/middleware/promiseMiddleware.js
import axios from 'axios'; export default store => next => action => { const {dispatch, getState} = store; /*若是dispatch來的是一個function,此處不作處理,直接進入下一級*/ if (typeof action === 'function') { action(dispatch, getState); } /*解析action*/ const { promise, types, afterSuccess, ...rest } = action; /*沒有promise,證實不是想要發送ajax請求的,就直接進入下一步啦!*/ if (!action.promise) { return next(action); } /*解析types*/ const [REQUEST, SUCCESS, FAILURE] = types; /*開始請求的時候,發一個action*/ next({ ...rest, type: REQUEST }); /*定義請求成功時的方法*/ const onFulfilled = result => { next({ ...rest, result, type: SUCCESS }); if (afterSuccess) { afterSuccess(dispatch, getState, result); } }; /*定義請求失敗時的方法*/ const onRejected = error => { next({ ...rest, error, type: FAILURE }); }; return promise(axios).then(onFulfilled, onRejected).catch(error => { console.error('MIDDLEWARE ERROR:', error); onRejected(error) }) }
修改src/redux/store.js
來應用這個中間件
import {createStore, applyMiddleware} from 'redux'; import combineReducers from './reducers.js'; import promiseMiddleware from './middleware/promiseMiddleware' let store = createStore(combineReducers, applyMiddleware(promiseMiddleware)); export default store;
修改src/redux/actions/userInfo.js
export const GET_USER_INFO_REQUEST = "userInfo/GET_USER_INFO_REQUEST"; export const GET_USER_INFO_SUCCESS = "userInfo/GET_USER_INFO_SUCCESS"; export const GET_USER_INFO_FAIL = "userInfo/GET_USER_INFO_FAIL"; export function getUserInfo() { return { types: [GET_USER_INFO_REQUEST, GET_USER_INFO_SUCCESS, GET_USER_INFO_FAIL], promise: client => client.get(`http://localhost:8080/api/user.json`) } }
是否是簡單清新不少啦?
修改src/redux/reducers/userInfo.js
case GET_USER_INFO_SUCCESS: return { ...state, isLoading: false, userInfo: action.result.data, errorMsg: '' };
action.userInfo
修改爲了action.result.data
。你看中間件,請求成功,會給action
增長一個result
字段來存儲響應結果哦~不用手動傳了。
npm start
看看咱們的網絡請求是否是正常哦。
使用自動編譯代碼時,可能會在保存文件時遇到一些問題。某些編輯器具備「安全寫入」功能,可能會影響從新編譯。
要在一些常見的編輯器中禁用此功能,請查看如下列表: