第七章 Redux和服务器通信
-
React中的代理
-
核实发起请求
fetch
参考mdn:
https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch
- Redux 访问服务器
-
redux-thunk 中间件
使用,引入文件,并加入到middlewares数组中即可。
实例:
store.js中引入redux-thunk
import {createStore, combineReducers, applyMiddleware, compose} from 'redux';
import thunkMiddleware from 'redux-thunk'
import {reducer as weatherReducer} from './weather/';
import Perf from 'react-addons-perf'
const win = window;
win.Perf = Perf
const reducer = combineReducers({
weather: weatherReducer
});
const middlewares = [thunkMiddleware];
if (process.env.NODE_ENV !== 'production') {
middlewares.push(require('redux-immutable-state-invariant')());
}
const storeEnhancers = compose(
applyMiddleware(...middlewares),
(win && win.devToolsExtension) ? win.devToolsExtension() : (f) => f,
);
export default createStore(reducer, {}, storeEnhancers);
action的类型actionTypes.js
export const FETCH_STARTED = 'WEATHER/FETCH_STARTED';
export const FETCH_SUCCESS = 'WEATHER/FETCH_SUCCESS';
export const FETCH_FAILURE = 'WEATHER/FETCH_FAILURE';
actions.js
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
})
export const fetchWeather = (cityCode) => {
return (dispatch) => {
const apiUrl = `/data/cityinfo/${cityCode}.html`;
dispatch(fetchWeatherStarted())
return 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) => {
dispatch(fetchWeatherFailure(error));
});
}).catch((error) => {
dispatch(fetchWeatherFailure(error));
})
};
}
reducer.js
import {FETCH_STARTED, FETCH_SUCCESS, FETCH_FAILURE} from './actionTypes.js';
import * as Status from './status.js';
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;
}
}
}
视图层,选择城市并派发一个初始城市
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {actions as weatherActions} from '../weather/';
const CITY_CODES = {
'北京': 101010100,
'上海': 101020100,
'广州': 101280101,
'深圳': 101280601
};
class CitySelector extends React.Component {
constructor() {
super(...arguments);
this.onChange = this.onChange.bind(this);
}
onChange(ev) {
const cityCode = ev.target.value;
this.props.onSelectCity(cityCode)
}
componentDidMount() {
const defaultCity = Object.keys(CITY_CODES)[0];
this.props.onSelectCity(CITY_CODES[defaultCity]);
}
render() {
return (
);
}
}
CitySelector.propTypes = {
onSelectCity: PropTypes.func.isRequired
};
const mapDispatchToProps = (dispatch) => {
return {
onSelectCity: (cityCode) => {
dispatch(weatherActions.fetchWeather(cityCode));
}
}
};
export default connect(null, mapDispatchToProps)(CitySelector);
视图层根据请求状态做不同显示
status.js
export const LOADING = 'loading';
export const SUCCESS = 'success';
export const FAILURE = 'failure';
天气视图
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import * as Status from './status.js';
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.propTypes = {
status: PropTypes.string.isRequired,
cityName: PropTypes.string,
weather: PropTypes.string,
lowestTemp: PropTypes.string,
highestTemp: PropTypes.string
};
const mapStateTopProps = (state) => {
const weatherData = state.weather;
return {
status: weatherData.status,
cityName: weatherData.city,
weather: weatherData.weather,
lowestTemp: weatherData.temp1,
highestTemp: weatherData.temp2
};
}
export default connect(mapStateTopProps)(Weather);
该进actions.js,终止异步
原理:每次请求都会被编号,当存在多次请求时,存在多个局部变量seqId,但是全局的nextSeqId是唯一的,而且总时最后一个,故通过下面的相等判断,总能返回最新一次请求的结果。
import {FETCH_STARTED, FETCH_SUCCESS, FETCH_FAILURE} from './actionTypes.js';
let nextSeqId = 0;
export const fetchWeatherStarted = () => ({
type: FETCH_STARTED
});
export const fetchWeatherSuccess = (result) => ({
type: FETCH_SUCCESS,
result
})
export const fetchWeatherFailure = (error) => ({
type: FETCH_FAILURE,
error
})
export const fetchWeather = (cityCode) => {
return (dispatch) => {
const apiUrl = `/data/cityinfo/${cityCode}.html`;
const seqId = ++ nextSeqId;
const dispatchIfValid = (action) => {
if (seqId === nextSeqId) {
return dispatch(action);
}
}
dispatchIfValid(fetchWeatherStarted())
fetch(apiUrl).then((response) => {
if (response.status !== 200) {
throw new Error('Fail to get response with status ' + response.status);
}
response.json().then((responseJson) => {
dispatchIfValid(fetchWeatherSuccess(responseJson.weatherinfo));
}).catch((error) => {
dispatchIfValid(fetchWeatherFailure(error));
});
}).catch((error) => {
dispatchIfValid(fetchWeatherFailure(error));
})
};
}