无论是React
还是Redux
,工作方式都是依靠数据驱动,在开发过程中,应用数据往往存储在数据库中,通过一个api服务器暴露出来,网页应用要获取数据,就需要与服务器进行通信。
React
组件访问服务器我们先来看一下一些比较简单的场景,在一些比较简单的应用中,我们可能只需要使用react
,而不使用redux
之类的数据管理框架,这时候react
组件自身也可以担当起和服务器通信的职责。
有很多JavaScript框架都支持和服务器进行通信,例如最传统的jQuery
的$.ajax()
函数。但是既然我们都已经使用了react
,就没有必要再为了一个ajax
函数在引入jQuery
这一个框架。
一个趋势是在react
应用中使用浏览器原生支持的fetch
函数来访问网络资源,fetch
函数返回的结果是一个Promise
对象,Promise
作为一个新推出的api,能够让异步处理的代码更加简洁清晰。现代浏览器大部分都已经原生支持了fetch
函数,对于不支持fetch
的浏览器版本,也可以通过fetch
的polyfill
来增加对fetch
的支持。这个polyfill
相当于是给全局的window
对象上增加了一个fetch
函数,让这个网页中的javascript
可以直接使用fetch
函数。
React
组件访问服务器的生命周期首先,访问服务器api是一个异步操作。而javascript
是单线程语言,不可能让主线程一直等待网络请求的结果。所以,所有对服务器的数据请求必定是一个异步操作。
但是,React
组件的渲染又是同步的,当开始渲染过程之后,不可能让组件一边渲染一边等待服务器的返回结果。
所以,我们一般这样去处理一个组件和服务器的通信:
loading
之类的提示信息。与此同时,组件发出对服务器的数据请求。从上面过程可以看出,为了完成一次组件和服务器之间的通信,必须要经历装载和更新过程,至少要渲染一个组件两次。
那么,在装载过程中,在什么时机发出对服务器的请求呢?
通常,我们在组件的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
中,要做的第一件事就是检查传入参数resp
的status
字段,只有status
是代表成功的200的时候才继续,否则以错误处理。
当resp.status
为200时,也不能直接读取resp
的内容,因为fetch
在接收到HTTP
响应的报头部分就会调用then
,不会等到整个HTTP
响应完成,所以这时候也不能保证能读到整个HTTP
报文的JSON
格式数据。所以,resp.body
函数执行并不是返回json
内容,而是返回一个新的Promise
,又要接着用then
和catch
来处理成功和失败的情况。
React
组件访问服务器的优缺点Redux
是用来帮助管理应用状态的,应该尽量把状态存放在Redux Store
中,而不是放在React
组件中。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
对象的生命周期,如果是函数,则执行这个函数,并把store
的dispatch
函数和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
构造函数的模式就是 函数体内返回一个新的函数,这个新的函数可以有两个参数dispatch
和getState
。分别代表redux
唯一store
上的成员函数dispatch
和getState
。