首页 男生 其他 深入浅出React和Redux

7.2.3 异步操作的模式

深入浅出React和Redux 程墨 11972 2021-04-06 02:29

  您可以在百度里搜索“深入浅出React和Redux 艾草文学(www.321553.xyz)”查找最新章节!

  

  7.2.3 异步操作的模式

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

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

  ·表示异步操作已经开始的action类型,在这个例子里,表示一个请求天气信息的API请求已经发送给服务器的状态;

  ·表示异步操作成功的action类型,请求天气信息的API调用获得了正确结果,就会引发这种类型的action;

  ·表示异步操作失败的action类型,请求天气信息的API调用任何一个环节出了错误,无论是网络错误、本地代理服务器错误或者是远程服务器返回的结果错误,都会引发这个类型的action。

  当这三种类型的action对象被派发时,会让React组件进入各自不同的三种状态,如下所示。

  ·异步操作正在进行中;

  ·异步操作已经成功完成;

  ·异步操作已经失败。

  不管网络的传输速度有多快,也不管远程服务器响应有多快,我们都不能认为“异步操作正在进行中”状态会瞬间转换为“异步操作已经成功完成”或者“异步操作已经失败”状态。前面说过,网络和远程服务器都是外部实体,是靠不住的。在开发环境下可能速度很快,所以感知不到状态转换,但在其他环境下可能会明显感觉存在延迟,所以有必要在视图上体现三种状态的区别。

  作为一种模式,我们需要定义三种action类型,还要定义三种对应的状态类型。

  相关代码在本书的Github代码库https://github.com/mocheng//react-and-redux的chapter-07/weather_redux目录下可以找到。

  和以往的习惯一样,我们为Weather组件创建一个放置所有代码的目录weather,对外接口的文件是src/weather/index.js,把这个功能模块的内容导出,代码如下:

  import * as actions from './actions.js';

  import reducer from './reducer.js';

  import view from './view.js';

  export {actions, reducer, view};

  在src/weather/actionTypes.js中定义异步操作需要的三种action类型:

  export const FETCH_STARTED = 'WEATHER/FETCH_STARTED';

  export const FETCH_SUCCESS = 'WEATHER/FETCH_SUCCESS';

  export const FETCH_FAILURE = 'WEATHER/FETCH_FAILURE';

  在src/weather/status.js文件中定义对应的三种异步操作状态:

  export const LOADING = 'loading';

  export const SUCCESS = 'success';

  export const FAILURE = 'failure';

  读者可能觉得actionTypes.js和status.js内容重复了,因为三个action类型和三个状态是一一对应的。虽然看起来代码重复,但是从语义上说,action类型只能用于action对象中,状态则是用来表示视图。为了语义清晰,还是把两者分开定义。

  接下来我们看src/weather/actions.js中action构造函数如何定义,首先是三个普通的action构造函数,代码如下:

  import {FETCH_STARTED, FETCH_SUCCESS, FETCH_FAILURE} from './actionTypes.js';

  export const fetchWeatherStarted = () => ({

  type: FETCH_STARTED

  });

  export const fetchWeatherSuccess = (result) => ({

  type: FETCH_SUCCESS,

  result

  })

  export const fetchWeatherFailure = (error) => ({

  type: FETCH_FAILURE,

  error

  })

  三个普通的action构造函数fetchWeatherStarted、fetchWeatherSuccess和fetchWeather-Failure没有什么特别之处,只是各自返回一个有特定type字段的普通对象,它们的作用是驱动reducer函数去改变Redux Store上weather字段的状态。

  关键是随后的异步action构造函数fetchWeather,代码如下:

  export const fetchWeather = (cityCode) => {

  return (dispatch) => {

  const apiUrl = `/data/cityinfo/${cityCode}.html`;

  dispatch(fetchWeatherStarted())

  fetch(apiUrl).then((response) => {

  if (response.status !== 200) {

  throw new Error('Fail to get response with status ' + response.status);

  }

  response.json().then((responseJson) => {

  dispatch(fetchWeatherSuccess(responseJson.weatherinfo));

  }).catch((error) => {

  throw new Error('Invalid json response: ' + error)

  });

  }).catch((error) => {

  dispatch(fetchWeatherFailure(error));

  })

  };

  }

  异步action构造函数的模式就是函数体内返回一个新的函数,这个新的函数可以有两个参数dispatch和getState,分别代表Redux唯一的Store上的成员函数dispatch和getState。这两个参数的传入是redux-thunk中间件的工作,至于redux-thunk如何实现这个功能,我们在后面关于中间件的章节会详细介绍。

  在这里,我们只要知道异步action构造函数的代码基本上都是这样的套路,代码如下:

  export const sampleAsyncAction = () => {

  return (dispatch, getState) => {

  //在这个函数里可以调用异步函数,自行决定在合适的时机通过dispatch参数

  //派发出新的action对象。

  }

  }

  在我们的例子中,异步action对象返回的新函数首先派发由fetchWeatherStarted的产生的action对象。这个action对象是一个普通action对象,所以会同步地走完单向数据流,一直走到reducer函数中,引发视图的改变。同步派发这个action对象的目的是将视图置于“有异步action还未结束”的状态,完成这个提示之后,接下来才开始真正的异步操作。

  这里使用fetch来做访问服务器的操作,和前面介绍的weather_react应用中的代码几乎一样,区别只是this.setState改变组件状态的语句不见了,取而代之的是通过dispatch来派发普通的action对象。也就是说,访问服务器的异步action,最后无论成败,都要通过派发action对象改变Redux Store上的状态完结。

  在fetch引发的异步操作完成之前,Redux正常工作,不会停留在fetch函数执行上,如果有其他任何action对象被派发,Redux照常处理。

  我们来看一看src/weather/reducer.js中的reducer函数,代码如下:

  export default (state = {status: Status.LOADING}, action) => {

  switch(action.type) {

  case FETCH_STARTED: {

  return {status: Status.LOADING};

  }

  case FETCH_SUCCESS: {

  return {...state, status: Status.SUCCESS, ...action.result};

  }

  case FETCH_FAILURE: {

  return {status: Status.FAILURE};

  }

  default: {

  return state;

  }

  }

  }

  在reducer函数中,完成了上面提到的三种action类型到三种状态类型的映射,增加一个status字段,代表的就是视图三种状态之一。

  这里没有任何处理异步action对象的逻辑,因为异步action对象在中间件层就被redux-thunk拦截住了,根本没有机会走到reducer函数中来。

  最后来看看src/weather/view.js中的视图,也就是React组件部分,首先是无状态组件函数,代码如下:

  const Weather = ({status, cityName, weather, lowestTemp, highestTemp}) => {

  switch (status) {

  case Status.LOADING: {

  return 天气信息请求中...;

  }

  case Status.SUCCESS: {

  return (

  {cityName} {weather} 最低气温 {lowestTemp} 最高气温 {highestTemp}

  )

  }

  case Status.FAILURE: {

  return 天气信息装载失败

  }

  default: {

  throw new Error('unexpected status ' + status);

  }

  }

  }

  和weather_react中的例子不同,因为现在状态都是存储在Redux Store上,所以这里Weather是一个无状态组件,所有的props都是通过Redux Store状态获得。

  在渲染函数中,根据三种不同的状态,显示出来的内容也不一样。当视图状态为LOADING时,表示一个对天气信息的API请求刚刚发出,还没有结果返回,这时候界面上就显示一个“天气信息请求中…”字样;当视图状态为SUCCESS时,根据状态显示对应的城市和天气信息;当视图状态为FAILURE时,显示“天气信息装载失败”。

  这个组件对应的mapStateToProps函数代码如下:

  const mapStateTopProps = (state) => {

  const weatherData = state.weather;

  return {

  status: weatherData.status,

  cityName: weatherData.city,

  weather: weatherData.weather,

  lowestTemp: weatherData.temp1,

  highestTemp: weatherData.temp2

  };

  }

  为了驱动Weather组件的action,我们另外创建一个城市选择器控件CitySelector,CitySelector很简单,也不是这个应用功能的重点,我们只需要它提供一个作为视图的React组件就可以。

  在CitySelector组件中,定义了四个城市的代码,代码如下:

  const CITY_CODES = {

  '北京': 101010100,

  '上海': 101020100,

  '广州': 101280101,

  '深圳': 101280601

  };

  CitySelector组件的render函数根据CITY_CODES的定义画出四个城市的选择器,代码如下:

  render() {

  return (

  {

  Object.keys(CITY_CODES).map(

  cityName => {cityName}

  )

  }

  );

  }

  }

  其中使用到的onChange函数使用onSelectCity来派发出action,代码如下:

  onChange(ev) {

  const cityCode = ev.target.value;

  this.props.onSelectCity(cityCode)

  }

  为了让网页初始化的时候就能够获得天气信息,在componentDidMount中派发了对应第一个城市的fetchWeatheraction对象,代码如下:

  componentDidMount() {

  const defaultCity = Object.keys(CITY_CODES)[0];

  this.props.onSelectCity(CITY_CODES[defaultCity]);

  }

  CitySelector的mapDispatchToProps函数提供了名为onSelectCity的函数类型prop,代码如下:

  const mapDispatchToProps = (dispatch) => {

  return {

  onSelectCity: (cityCode) => {

  dispatch(weatherActions.fetchWeather(cityCode));

  }

  }

  };

  这个city_selector提供的视图导入了weather功能组件导出的actions,显示出北京、上海、广州、深圳四个城市的选择器,当用户选中某个城市的时候,就会派发fetchWeather构造器产生的action对象,让Weather组件去服务器获取对应城市的天气信息。

  完成全部代码之后,我们在网页中就可以看到最终效果,如图7-3所示。

  图7-3 天气应用最终效果

  当我们选择另一个城市之后,可以看到会有短暂的显示“天气信息请求中...”,然后才显示出对应城市的天气,因为访问服务器总是会有时间延迟。

  我们也可以试着关闭命令行上中断npm start这个命令,等于是关闭了代理服务器,这样对API的访问必然失败,然后切换城市,可以看到“天气信息装载失败”。 深入浅出React和Redux

目录
设置
手机
书架
书页
评论