上一篇已經講了一些react的基本配置,本遍接着講熱更新以及react+redux的配置與使用。html
咱們在實際開發時,都有用到熱更新,在修改代碼後,不用每次都重啓服務,而是自動更新。並而不是讓瀏覽器刷新,只是刷新了咱們所改代碼影響到的模塊。
關於熱更新的配置,可看介紹戳這裏node
由於咱們用了webpack-dev-server,咱們能夠不須要向上圖同樣配置,只須要修改啓動配置以修改默認值,--hot項。react
"start": "webpack-dev-server --config webpack.dev.config.js --color --progress --hot"
而後要作的是當模塊更新後,通知入口文件index.js。咱們看官網的教程配置
webpack
打開src/index.js,如上圖配置git
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'); )
下面來試試重啓後,修改Home或About組件,保存後是否是自動更新啦!
github
到這裏,你覺得結束了嗎,NO!NO!NO!在此咱們成功爲本身挖下了坑(說多了都是淚)。獻上一段demo
src/pages/Home/Home.jsweb
import React,{Component} from 'react'; export default class Home extends Component{ constructor(props){ super(props); this.state={ count:0 } } _test(){ this.setState({ count:++this.state.count }); } render(){ return( <div> <h1>當前共點擊次數爲:{this.state.count}</h1> <button onClick={()=> this._test()}>點擊我!</button> </div> ) } }
此時,按鈕每點擊一次,狀態會自增,可是若是咱們用熱更新改一下文件,會發現,狀態被清零了!!!顯然這不是咱們要的效果,那麼咱們平時在項目裏爲何會用到react-hot-loader就明瞭了,由於能夠保存狀態。試試:
安裝依賴shell
npm install react-hot-loader --save-dev
按官網介紹來配置npm
{ "plugins":["react-hot-loader/babel"] }
entry:[ 'react-hot-loader/patch', path.join(__dirname,'src/index.js') ]
import React from 'react'; import ReactDom from 'react-dom'; import getRouter from './router/router'; import {AppContainer} from 'react-hot-loader'; const hotLoader = RootElement => { ReactDom.render( <AppContainer> {RootElement} </AppContainer>, document.getElementById('app') ); } /*初始化*/ hotLoader(getRouter()); if(module.hot){ module.hot.accept('./router/router',()=>{ const getRouter=require('./router/router').default; hotLoader(getRouter()); }); }
哇哦哇哦,成功保存狀態啦,666!json
上面的demo咱們已經寫過好幾個組件了,發如今引用的時候都要用上相對路徑,這樣很是不方便。咱們能夠優化一下。
咱們之前作數學題總會尋找一些共同點提出來,這裏也同樣。咱們的公共組件都放在了src/components文件目錄下,業務組件都放在src/pages目錄下。在webpack中,提供一個別名配置,讓咱們不管在哪一個位置下,都經過別名從對應位置去讀取文件。
修改webpack.dev.config.js
resolve:{ alias:{ pages:path.join(__dirname,'src/pages'), components:path.join(__dirname,'src/components'), router:path.join(__dirname,'src/router') } }
而後按下面的形式改掉以前的路徑
/*以前*/ import Home from '../pages/Home/Home'; /*以後*/ import Home from 'pages/Home/Home';
看下改了路徑後,是否是依然能夠正常運行呢!
若是用react作過項目的,基本對redux就不陌生了吧。此文主講全家桶的搭建,在此我就不詳細解說。簡單說下引用,作個小型計數器。
npm install --save redux
cd src mkdir redux && cd redux mkdir actions mkdir reducers touch reducer.js touch store.js touch actions/counter.js touch reducers/counter.js
alias:{ ... actions:path.join(__dirname,'src/redux/actions'), reducers:path.join(__dirname,'src/redux/reducers'), //redux:path.join(__dirname,'src/redux') 與模塊重名 }
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} }
import {INCREMENT,DECREMENT,RESET} from '../actions/counter'; const initState = { count : 0 }; 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 } }
import counter from './rdeducers/counter'; export default function combineReducers(state={},action){ return { counter:counter(state.counter,action) } }
import {createStore} from 'redux'; import combineReducers from './reducers.js'; let store = createStore(combineReducers); export default store;
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()); //監聽每次更新值 let unsubscribe = store.subscribe(() => console.log(store.getState()) ); //發起action store.dispatch(increment()); store.dispatch(decrement()); store.dispatch(reset()); //中止監聽 unsubscribe();
在當前目錄下運行
webpack testRedux.js build.js node build.js
我這裏報以下錯誤了
經排查,發現是node版本的問題,我用nvm來做node版本管理工具,從本來的4.7切換到9.0的版本,運行正確。
咱們試用了一下redux,對於在項目熟用的童鞋來講,簡直是沒難度吧。那麼迴歸正題,咱們用redux搭配着react一塊兒用。將上述counter改爲一個組件。
cd src/pages mkdir Counter touch Counter/Counter.js
打開文件
import React,{Component} from 'react'; export default class Counter extends Component{ render(){ return( <div> <h2>當前計數爲:</h2> <button onClick={ ()=>{ console.log('自增'); } }>自增 </button> <button onClick={()=>{ console.log('自減'); }}>自減 </button> <button onClick={()=>{ console.log('重置') }}>重置 </button> </div> ) } }
import Home from 'pages/Home/Home'; import About from 'pages/About/About'; import Counter from 'pages/Counter/Counter'; const getRouter=()=>( <Router> <div> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/about">About</Link></li> <li><Link to="counter">Counter</Link></li> </ul> <Switch> <Route exact path="/" component={Home}/> <Route path="/about" component={About}/> <Route path="/counter" component={Counter}/> </Switch> </div> </Router> ); export default getRouter;
咱們能夠先跑一下,檢查路由跳轉是否正常。下面將redux應用到Counter組件上。
npm install --save react-redux
由於react-redux提供了connect方法,接收兩個參數。
import React,{Component} from 'react'; import {increment,decrement,reset} from 'actions/counter'; import {connect} from 'react-redux'; class Counter extends Component{ render(){ return( <div> <h2>當前計數爲:{this.props.counter.count}</h2> <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);
... import {Provider} from 'react-redux'; import store from './redux/store'; const hotLoader = RootElement => { ReactDom.render( <AppContainer> <Provider store={store}> {RootElement} </Provider> </AppContainer>, document.getElementById('app') ); } ...
而後咱們運行下,效果如圖
在實際開發中,咱們更多的是用異步action,由於要先後端聯合起來處理數據。
正常咱們去發起一個請求時,給用戶呈現的大概步驟以下:
下面咱們模擬一個用戶信息的get請求接口:
cd dist mkdir api && cd api touch userInfo.json
{ "name":"circle", "age":24, "like":"piano", "female":"girl" }
cd src/redux/actions touch userInfo.js
在action中,我要須要建立三種狀態:請求中,請求成功,請求失敗。打開redux/actions/userInfo.js
export const GET_USERINFO_REQUEST="userInfo/GET_USERINFO_REQUEST"; export const GET_USERINFO_SUCCESS="userInfo/GET_USERINFO_SUCCESS"; export const GET_USERINFO_FAIL="userInfo/GET_USERINFO_FAIL"; export function getUserInfoRequest(){ return { type:GET_USERINFO_REQUEST } } export function getUserInfoSuccess(userInfo){ return{ type:GET_USERINFO_SUCCESS, userInfo:userInfo } } export function getUserInfoFail(){ return{ type:GET_USERINFO_FAIL } }
cd src/redux/reducers touch userInfo.js
打開文件
import {GET_USERINFO_REQUEST,GET_USERINFO_SUCCESS,GET_USERINFO_FAIL} from 'actions/userInfo'; const initState = { isLoading:false, userInfo:{}, errMsg:'' } export default function reducer(state=initState,action){ switch(action.type){ case GET_USERINFO_REQUEST: return{ ...state, isLoading:true, userInfo:{}, errMsg:'' } case GET_USERINFO_SUCCESS: return{ ...state, isLoading:false, userInfo:action.userInfo, errMsg:'' } case GET_USERINFO_FAIL: return{ ...state, isLoading:false, userInfo:{}, errMsg:'請求出錯' } default: return state; } }
以上...state的意思是合併新舊的全部state可枚舉項。
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) } }
redux中提供了一個combineReducers函數來合併reducer,不須要咱們本身寫合併函數,在此咱們對上面的reducers.js做下優化。
import counter from 'reducers/counter'; import userInfo from 'reducers/userInfo'; import {combineReducers} from 'redux'; export default combineReducers({ counter, userInfo });
... export function getUserInfo(){ return function(dispatch){ dispatch(getUserInfoRequest()); return fetch('http://localhost:8000/api/userInfo.json') .then((response=>{ return response.json() })) .then((json)=>{ dispatch(getUserInfoSuccess(json)) } ).catch(()=>{ dispatch(getUserInfoFail()); } ) } }
以前咱們作計數器時,與之對比現發action都是返回的對象,這裏咱們返回的是函數。
爲了讓action能夠返回函數,咱們須要裝新的依賴redux-tuhnk。它的做用是在action到reducer時做中間攔截,讓action從函數的形式轉爲標準的對象形式,給reducer做正確處理。
npm install --save redux-thunk
import {createStore,applyMiddleware} from 'redux'; import combineReducers from './reducers.js'; import thunkMiddleware from 'redux-thunk'; let store = createStore(combineReducers,applyMiddleware(thunkMiddleware)); export default store;
到這裏咱們基本的redux就搞定啦,下面寫個組件來驗證。
cd src/pages mkdir UserInfo && cd UserInfo touch 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,errMsg} = this.props.userInfo; return( <div> { isLoading ? '請求中...' : ( errMsg ? errMsg : <div> <h2>我的資料</h2> <ul> <li>姓名:{userInfo.name}</li> <li>年齡:{userInfo.age}</li> <li>愛好:{userInfo.like}</li> <li>性別:{userInfo.female}</li> </ul> </div> ) } <button onClick={ ()=> this.props.getUserInfo() }>查看我的資料</button> </div> ) } } export default connect((state)=>({userInfo:state.userInfo}),{getUserInfo})(UserInfo);
... import React from 'react'; import {BrowserRouter as Router,Route,Switch,Link} from 'react-router-dom'; import Home from 'pages/Home/Home'; import About from 'pages/About/About'; import Counter from 'pages/Counter/Counter'; import UserInfo from 'pages/UserInfo/UserInfo'; const getRouter=()=>( <Router> <div> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/about">About</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="/about" component={About}/> <Route path="/counter" component={Counter}/> <Route path="/userinfo" component={UserInfo}/> </Switch> </div> </Router> ); export default getRouter;
個人博客即將搬運同步至騰訊雲+社區,邀請你們一同入駐:https://cloud.tencent.com/developer/support-plan