手把手教你全家桶之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的操作
  1. 应用中state的保持
  2. getState()获取state
  3. dispatch(action)触发reducers,改变state
  4. 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方法,接收两个参数。

  1. mapStateToProps:把redux的state,转为组件的Props;
  2. 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,因为要前后端联合起来处理数据。 正常我们去发起一个请求时,给用户呈现的大概步骤如下:

  1. 页面加载,请求发起,出现loading效果
  2. 请求成功,停止loading效果,data渲染
  3. 请求失败,停止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;
  • 运行效果如下

未完待续 ^_^ (偷偷告诉你,第三篇会讲一些优化哦!)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏DannyHoo的专栏

利用UIPanGestureRecognizer手势全屏侧滑返回

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010105969/article/details/...

732
来自专栏Android干货园

Android高仿微信照片选择器+预览+显示照片

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/49...

562
来自专栏向治洪

搜索附近人和商铺功能

越来越多的Android应用都加入了“附近的人”的功能,比如微信、陌陌、淘宝等,今天分享一个demo,简单的来实现这一功能。主要原理为:手机端上传gps数据到...

1855
来自专栏蘑菇先生的技术笔记

Windows10-UWP中设备序列显示不同XAML的三种方式[3]

1743
来自专栏贾鹏辉的技术专栏@CrazyCodeBoy

React Native iOS原生模块开发实战|教程|心得

尊重版权,未经授权不得转载 本文出自:贾鹏辉的技术博客(http://www.devio.org) 告诉大家一个好消息,为大家精心准备的React N...

4666
来自专栏前端新视界

构建具有用户身份认证的 React + Flux 应用程序

React 的生态系统很大,为了解决 React 中比较困难的问题,你可以选择多种模块。大多数实际的 React 应用程序都有一些共同的需求,这些需求主要包括状...

1010
来自专栏跟着阿笨一起玩NET

DataGridView列自适应宽度

来源:http://www.cnblogs.com/wolf-sun/p/3480104.html

481
来自专栏逢魔安全实验室

基于Chorme headless的xss检测实践

https://blog.formsec.cn/2018/07/12/%E5%9F%BA%E4%BA%8EChrome-headless%E7%9A%84XSS...

1264
来自专栏前端新视界

构建具有用户身份认证的 React + Flux 应用程序

原文:Build a React + Flux App with User Authentication 译者:nzbin 译者的话:这是一篇内容详实的 Rea...

3817
来自专栏青蛙要fly的专栏

项目需求讨论-Retrofit中文提交及上传头像功能

很早就开通了掘金上发表文章权限,但一直没有在掘金上写,都是在简书上面写好,然后偷懒在掘金上直接就网址分享链接。O(∩_∩)O~这次就上来写了。

763

扫码关注云+社区