深入浅出React之第六章:Redux和服务器通信

来源:互联网 发布:lol mac国服怎么取消了 编辑:程序博客网 时间:2024/06/06 03:22

无论是React还是Redux,工作方式都是依靠数据驱动,在开发过程中,应用数据往往存储在数据库中,通过一个api服务器暴露出来,网页应用要获取数据,就需要与服务器进行通信。

1.React组件访问服务器

我们先来看一下一些比较简单的场景,在一些比较简单的应用中,我们可能只需要使用react,而不使用redux之类的数据管理框架,这时候react组件自身也可以担当起和服务器通信的职责。

有很多JavaScript框架都支持和服务器进行通信,例如最传统的jQuery$.ajax()函数。但是既然我们都已经使用了react,就没有必要再为了一个ajax函数在引入jQuery这一个框架。

一个趋势是在react应用中使用浏览器原生支持的fetch函数来访问网络资源,fetch函数返回的结果是一个Promise对象,Promise作为一个新推出的api,能够让异步处理的代码更加简洁清晰。现代浏览器大部分都已经原生支持了fetch函数,对于不支持fetch的浏览器版本,也可以通过fetchpolyfill来增加对fetch的支持。这个polyfill相当于是给全局的window对象上增加了一个fetch函数,让这个网页中的javascript可以直接使用fetch函数。

1.1 React组件访问服务器的生命周期

首先,访问服务器api是一个异步操作。而javascript是单线程语言,不可能让主线程一直等待网络请求的结果。所以,所有对服务器的数据请求必定是一个异步操作。

但是,React组件的渲染又是同步的,当开始渲染过程之后,不可能让组件一边渲染一边等待服务器的返回结果。

所以,我们一般这样去处理一个组件和服务器的通信:

  • 步骤 1 :在装载过程中,由于组件没有获取服务器结果,就不显示结果,或者显示一个loading之类的提示信息。与此同时,组件发出对服务器的数据请求。
  • 步骤 2:当对服务器的请求获取结果时,引发组件的一次更新过程,让组件重新绘制自己的内容,显示服务器数据。

从上面过程可以看出,为了完成一次组件和服务器之间的通信,必须要经历装载和更新过程,至少要渲染一个组件两次。

那么,在装载过程中,在什么时机发出对服务器的请求呢?

通常,我们在组件的componentDidMount函数中做请求服务器的事情。因为当生命周期函数componentDidMount被调用时,表明装载过程已经完成,组件需要渲染的内容已经在DOM树上出现,对服务器的请求可能依赖于已经渲染的内容,在componentDidMount函数中发送对服务器请求是一个合适的时机。

示例代码如下:

import React,{Component} from 'react';class Weather extends Component{    constructor(props){        super(props);        this.state={            info:null        }    }    componentDidMount(){        const apiUrl=`http://localhost:7760/service/xxx`;        fetch(apiUrl).then((resp)=>{            if(resp.status!==200) throw new Error('fail to get response with status:'+resp.status);            resp.json().then((respJson)=>{                this.setState({                    info:respJson.info                });            }).catch((err)=>{                this.setState({                    info:null                });            })        }).catch((err)=>{            this.setState({                info:null            });        })    }    render(){        if(!this.state.info) return <div>暂无数据</div>        const {info}=this.state;        return (            <div>{info}</div>        )    }}

虽然fetch现在被广为接受,但是它有一个特性一直被人诟病,那就是fetch认为只要服务器返回一个合法的http响应就算成功,就会调用then提供的回调函数,即使这个http响应的状态码是表示出错的400或者500。所以,我们在then中,要做的第一件事就是检查传入参数respstatus字段,只有status是代表成功的200的时候才继续,否则以错误处理。

resp.status为200时,也不能直接读取resp的内容,因为fetch在接收到HTTP响应的报头部分就会调用then,不会等到整个HTTP响应完成,所以这时候也不能保证能读到整个HTTP报文的JSON格式数据。所以,resp.body
函数执行并不是返回json内容,而是返回一个新的Promise,又要接着用thencatch来处理成功和失败的情况。

1.2 React组件访问服务器的优缺点

  • 优点:简单直接,容易理解。
  • 缺点:把状态存放在组件中实在不是一个很好的选择,尤其是当组件变得庞大复杂了之后。Redux是用来帮助管理应用状态的,应该尽量把状态存放在Redux Store中,而不是放在React组件中。

2. Redux访问服务器

使用redux访问服务器,同样要解决的是异步问题。我们这里使用Redux-thunk来处理redux中的异步操作。

redux单向数据流同步操作流程

驱动redux流程的是action对象,每一个action对象被派发到store上之后,同步的被分配到所有的reducer函数,每个reducer都是纯函数,不会产生任何的副作用,完成数据操作之后立刻同步返回,reducer返回的结果又被拿去更新store上的状态数据,更新状态数据的操作会立刻被同步给监听store状态改变的函数,从而引发react视图组件的更新。
这里写图片描述

redux-thunk中间件

按照redux-thunk的想法,在redux单向数据流中,在action对象被reducer函数处理之前,是插入异步功能的时机。

redux架构下,一个action对象在通过store.dispatch派发,在调用reducer函数之前,会经过一个中间件的环节,这里就是产生异步操作的机会。
这里写图片描述

异步action对象

当我们想要让redux帮忙处理一个异步操作的时候,代码一样要派发一个action对象,毕竟redux单向数据流就是由action对象驱动的。但是这个action对象比较特殊,我们叫他异步action对象。这个异步action对象可不是一个普通的javascript对象,而是一个函数。

如果没有redux-thunk中间件的存在,这样一个函数类型的action对象会被派发到各个reducer函数,reducer函数从这些实际上是函数的action对象上是无法获取type字段的,所以也做不了什么实质性的处理。

不过,由于redux-thunk这个中间件的存在,这些action对象根本没机会接触到reducer函数,在中间件一层就会被redux-thunk截获。

redux-thunk的工作是检查action对象是不是函数,如果不是函数就放行,完成普通的action对象的生命周期,如果是函数,则执行这个函数,并把storedispatch函数和getState函数作为参数传递到函数中去,处理过程到此为止,不会让这个异步action对象继续往前派发到reducer函数。

action对象函数中完全可以通过fetch发起一个对服务器的异步请求,当得到服务器结果之后,通过参数dispatch,把失败或者成功的结果当做action对象派发出去,由于这一次派发的是一个普通的action对象,因此不会被redux-thunk截获,而是直接派发到reducer,驱动store上状态的改变。

异步操作的模式

有了redux-thunk的帮助,我们可以使用异步action对象来完成异步访问服务器的功能了。在此之前,我们先想一想如何设计action类型和视图。

一个访问服务器的action,至少要涉及三个action类型:

  • 表示异步操作已经开始的action类型
  • 表示异步操作成功的action类型
  • 表示异步操作失败的action类型

当这三种类型的action对象被派发时,会让react组件进入各自不同的三种状态:

  • 异步操作正在进行中
  • 异步操作已经成功完成
  • 异步操作已经失败

下面,我们来看一下action构造函数如何定义:

import {FETCH_STARTED,FETCH_SUCCESS,FETCH_ERROR} from './actionTypes.js';export const fetchStarted=()=>({    type:FETCH_STARTED});export const fetchSuccess=(result)=>({    type:FETCH_SUCCESS,    result});export const fetchError=(error)=>({    type:FETCH_ERROR,    error});export const fetchAction=(url)=>{    return (dispatch)=>{        dispatch(fetchStarted());        fetch(url).then((resp)=>{            if(resp.status!==200) throw new Error('there is an error,resp status is :'+resp.status);            resp.json().then((respJson)=>{                dispatch(fetchSuccess(respJson.info));            }).catch((error)=>{                throw new Error('Invalid Json resp:'+error);            })        }).catch((error)=>{            dispatch(fetchError(error));        })    }}

异步action构造函数的模式就是 函数体内返回一个新的函数,这个新的函数可以有两个参数dispatchgetState。分别代表redux唯一store上的成员函数dispatchgetState