手把手教你全家桶之React(二)
前言
上一篇已经讲了一些react的基本配置,本遍接着讲热更新以及react+redux的配置与使用。
热更新
我们在实际开发时,都有用到热更新,在修改代码后,不用每次都重启服务,而是自动更新。并而不是让浏览器刷新,只是刷新了我们所改代码影响到的模块。
关于热更新的配置,可看介绍戳这里
因为我们用了webpack-dev-server,我们可以不需要向上图一样配置,只需要修改启动配置以修改默认值,--hot项。
"start": "webpack-dev-server --config webpack.dev.config.js --color --progress --hot"
然后要做的是当模块更新后,通知入口文件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');
)
下面来试试重启后,修改Home或About组件,保存后是不是自动更新啦!
到这里,你以为结束了吗,NO!NO!NO!在此我们成功为自己挖下了坑(说多了都是泪)。献上一段demo
src/pages/Home/Home.js
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就明了了,因为可以保存状态。试试:
安装依赖
npm install react-hot-loader --save-dev
按官网介绍来配置
- 首先是.babelrc文件
{
"plugins":["react-hot-loader/babel"]
}
- 修改 webpack.dev.config.js
entry:[
'react-hot-loader/patch',
path.join(__dirname,'src/index.js')
]
- 修改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!
路径的优化
上面的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';
看下改了路径后,是不是依然可以正常运行呢!
Redux
如果用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
- 增加文件的别名
打开webpack.dev.config.js
alias:{
...
actions:path.join(__dirname,'src/redux/actions'),
reducers:path.join(__dirname,'src/redux/reducers'),
//redux:path.join(__dirname,'src/redux') 与模块重名
}
- 创建action,action是来描述不同的场景,通过触发action进入对应reducer
打开文件src/redux/actions/counter.js
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}
}
- 接下来写reducers,用来接收action和旧的state,生成新的state
src/redux/reducers/counter.js
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
}
}
- 将所有的reducers合并到一起
src/redux/reducers.js
import counter from './rdeducers/counter';
export default function combineReducers(state={},action){
return {
counter:counter(state.counter,action)
}
}
- 创建store仓库,进行存取与监听state的操作
- 应用中state的保持
- getState()获取state
- dispatch(action)触发reducers,改变state
- subscribe(listener)注册监听器
打开src/redux/store.js
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>
)
}
}
- 路由增加
router/router.js
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组件上。
react-redux
- 安装 react-redux
npm install --save react-redux
- 组件的state绑定
因为react-redux提供了connect方法,接收两个参数。
- mapStateToProps:把redux的state,转为组件的Props;
- mapDispatchToprops:触发actions的方法转为Props属性函数。
connect()的作用有两个:一是从Redux的state中读取部分的数据,并通过props把这些数据返回渲染到组件中;二是传递dispatch(action)到props。
打开 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>
<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);
- 调用的用的时候到src/index.js中,我们传入store
注:我们引用react-redux中的Provider模块,它可以让所有的组件能访问到store,不用手动去传,也不用手动去监听。
...
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
在实际开发中,我们更多的是用异步action,因为要前后端联合起来处理数据。
正常我们去发起一个请求时,给用户呈现的大概步骤如下:
- 页面加载,请求发起,出现loading效果
- 请求成功,停止loading效果,data渲染
- 请求失败,停止loading效果,返回错误提示。
下面我们模拟一个用户信息的get请求接口:
- 创建文件
cd dist
mkdir api && cd api
touch userInfo.json
- 打开文件模拟数据
{
"name":"circle",
"age":24,
"like":"piano",
"female":"girl"
}
- 创建action
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
}
}
- 创建reducer
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可枚举项。
- 与之前做计数器一样,接下来到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)
}
}
redux中提供了一个combineReducers函数来合并reducer,不需要我们自己写合并函数,在此我们对上面的reducers.js作下优化。
import counter from 'reducers/counter';
import userInfo from 'reducers/userInfo';
import {combineReducers} from 'redux';
export default combineReducers({
counter,
userInfo
});
- 接下来发起请求
打开文件 src/redux/actions/userInfo.js,加入
...
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
- 引入redux-thunk,打开src/redux/store.js
我们可以使用Redux提供的applyMiddleware方法来使用一个或者是多个中间件,将它作为createStore的第二个参数传入即可。
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);
- 配置路由,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 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
手把手教你全家桶之React(二)的更多相关文章
- 手把手教你全家桶之React(三)--完结篇
前言 本篇主要是讲一些全家桶的优化与完善,基础功能上一篇已经讲得差不多了.直接开始: Source Maps 当javaScript抛出异常时,我们会很想知道它发生在哪个文件的哪一行.但是webpac ...
- 手把手教你全家桶之React(一)
前言 最近项目用到react,其实前年我就开始接触react,时光匆匆,一直没有时间整理下来(太懒啦)!如今再次用到,称工作间隙,对全家桶做一次总结,项目源码地址.废话不多说,上码. 创建一个文件目录 ...
- 手把手教你用webpack3搭建react项目(开发环境和生产环境)(一)
开发环境和生产环境整个配置源码在github上,源码地址:github-webpack-react 如果觉得有帮助,点个Star谢谢!! (一)是开发环境,(二)是生产环境. 一.首先创建packag ...
- vue全家桶和react全家桶
vue全家桶:vue + vuex (状态管理) + vue-router (路由) + vue-resource +axios +elementui react全家桶 : react + re ...
- Android开发之手把手教你写ButterKnife框架(二)
欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52664112 本文出自:[余志强的博客] 上一篇博客Android开 ...
- 手把手教你用Pytorch-Transformers——实战(二)
本文是<手把手教你用Pytorch-Transformers>的第二篇,主要讲实战 手把手教你用Pytorch-Transformers——部分源码解读及相关说明(一) 使用 PyTorc ...
- EOS基础全家桶(十二)智能合约IDE-VSCode
简介 上一篇我们介绍了EOS的专用IDE工具EOS Studio,该工具的优势是简单,易上手,但是灵活性低,且对系统资源开销大,依赖多,容易出现功能异常.那么我们开发人员最容易使用的,可能还是深度定制 ...
- 手把手教你用JS/Vue/React实现幸运水果机(80后情怀之作)
项目体验地址 免费视频教程 分别使用原生JS,Vue和React,手把手教你开发一个H5小游戏,快速上手Vue和React框架的使用. 项目截图 在线体验 在线体验 游戏介绍 幸运水果机是一款街机游戏 ...
- 手把手教你如何使用webpack+react
上一篇随笔讲述了新手入门入门前端 里面提到的第四阶段跟上当前前端的发展需要入门一个框架和自动化工具,当时推荐的是webpack+react 今天正好有空,也把自己入门webpack + react 的 ...
随机推荐
- 清除行内元素之间的HTML空白
原文连接:Remove Whitespace Between Inline-Block Elements 原文日期: 2013年8月27日 翻译日期: 2013年8月28日 至今我还记得年轻是在IE6 ...
- C++ Primer 有感(标准库set类型)
set容器只是单纯的键的集合,键必须为一.set容器不支持下标操作,而且没有定义maped_type类型.在set容器中,value_type不是pair类型,而是与key_type类型相同的类型. ...
- javascript之DOM编程实现城市的联动框
需求;用一张图片表示. 分析: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "ht ...
- golang:使用timingwheel进行大量ticker的优化
Ticker 最近的项目用go实现的服务器需要挂载大量的socket连接.如何判断连接是否还存活就是我们需要考虑的一个问题了. 通常情况下面,socket如果被客户端正常close,服务器是能检测到的 ...
- matlab学习日志之并行运算
原文地址:matlab并行计算,大家共同学习吧,涉及到大规模数据量处理的时候还是效果很好的 今天搞了一下matlab的并行计算,效果好的出乎我的意料. 本来CPU就是双核,不过以前一直注重算法,没注意 ...
- C++大小写转换和性能
p.MsoNormal,li.MsoNormal,div.MsoNormal { margin: 0cm; margin-bottom: .0001pt; text-align: justify; f ...
- laydate日期空间与时间选择器
http://laydate.layui.com/
- Gradle 1.12用户指南翻译——第三十九章. IDEA 插件
本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
- linux下64位汇编的系统调用(4)
经过上一篇的铺垫貌似可以很轻松的用汇编写出mmap的代码来,可仔细一看,还是有不少问题需要解决: 1.系统调用mmap如果出错并不直接返回MAP_FAILED(-1),而是一个"类似&quo ...
- LeetCode(26)-Binary Tree Level Order Traversal II
题目: Given a binary tree, return the bottom-up level order traversal of its nodes' values. (ie, from ...