上一篇已经讲了一些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