原文连接React Native实现一个带筛选功能的搜房列表(2)git
在上一篇中,咱们实现了一个下拉刷新和上拉加载更多的列表,那根据通常的开发步骤,接着应该就是进行网络请求,在网络请求以后更新列表数据和列表的刷新状态。github
这篇文章会向你们介绍一下Redux的基本概念以及在页面中如何使用Redux进行状态管理。redux
文章中的代码都来自代码传送门--NNHybrid。开始以前,咱们先看一下最终实现的效果 api
首先先简单介绍一下Redux的一些概念。Redux是JavaScript状态容器,提供可预测化的状态管理,其工做流程以下:缓存
整个工做流程为: bash
Store是存储state的容器,负责提供全部的状态。整个应用只能有一个Store,这么作的目的是为了让组件之间的通讯更加简单。网络
在没有Store的状况下,组件之间须要通讯就比较麻烦,若是一个父组件要将状态传递到子组件,就须要经过props一层一层往下传,一个子组件的状态发生改变而且要让父组件知道,则必须暴露一个事件出去才能通讯。这就使得组件之间通讯依赖于组件的层次结构。此时若是有两个平级的节点想要通讯,就须要经过它们的父组件进行中转。 有了这个全局的Store以后,全部的组件变成了和Store进行通讯。这样组件之间通讯就会变少,当Store发生变化,对应的组件也能拿到相关的数据。当组件内部有时间触发Store的变化时,更新Store便可。这也就是所谓的单向数据流过程。app
Store的职责以下:函数
getState()
方法获取state;dispatch(action)
方法更新state;subscribe(listener)
注册监听器;subscribe(listener)
返回的函数注销监听器。当咱们想要更改store中的state时,咱们便须要使用Action。Action是Store数据的惟一来源,每一次修改state便要发起一次Action。ui
Action能够理解为是一个Javascript对象。其内部必须包含一个type
字段来表示将要执行的动做,除了 type
字段外,Action的结构彻底由本身决定。多数状况下,type
字段会被定义成字符串常量。
Action举例:
{
type: Types.SEARCH_HOUSE_LOAD_DATA_SUCCESS,
currentPage: ++currentPage,
houseList,
hasMoreData,
}
复制代码
Action建立函数就是生成action的方法。“action” 和 “action 建立函数” 这两个概念很容易混在一块儿,使用时最好注意区分。
Action建立函数举例:
export function init(storeName) {
return dispatch => {
dispatch({ type: Types.HOUSE_DETAIL_INIT, storeName });
}
}
复制代码
Store收到Action之后,必须给出一个新的State,这样View才会发生变化。 这种State的计算过程就叫作Reducer。Reducer是一个纯函数,它只接受Action和当前State做为参数,返回一个新的State。
因为Reducer是一个纯函数,因此咱们不能在reducer里执行如下操做:
Reducer举例:
const defaultState = {
locationCityName: '',
visitedCities: [],
hotCities: [],
sectionCityData: [],
sectionTitles: []
};
export function cityListReducer(state = defaultState, action) {
switch (action.type) {
case Types.CITY_LIST_LOAD_DATA:
return {
...state,
visitedCities: action.visitedCities,
hotCities: action.hotCities,
sectionCityData: action.sectionCityData,
sectionTitles: action.sectionTitles,
}
case Types.CITY_LIST_START_LOCATION:
case Types.CITY_LIST_LOCATION_FINISHED:
return {
...state,
locationCityName: action.locationCityName
};
default:
return state;
}
}
复制代码
在开发过程当中,因为有的功能是相互独立的,因此咱们须要拆分reducer。通常状况下,针对一个页面能够设置一个reducer。但redux原则是只容许一个根reducer,接下来咱们须要将每一个页面的的reducer聚合到一个根reducer中。
合并reducer代码以下:
const appReducers = combineReducers({
nav: navReducer,
home: homeReducer,
cityList: cityListReducer,
apartments: apartmentReducer,
houseDetails: houseDetailReducer,
searchHouse: searchHouseReducer,
});
export default (state, action) => {
switch (action.type) {
case Types.APARTMENT_WILL_UNMOUNT:
delete state.apartments[action.storeName];
break;
case Types.HOUSE_DETAIL_WILL_UNMOUNT:
delete state.houseDetails[action.storeName];
break;
case Types.SEARCH_HOUSE_WILL_UNMOUNT:
delete state.searchHouse;
break;
}
return appReducers(state, action);
}
复制代码
SEARCH_HOUSE_LOAD_DATA: 'SEARCH_HOUSE_LOAD_DATA',
SEARCH_HOUSE_LOAD_MORE_DATA: 'SEARCH_HOUSE_LOAD_MORE_DATA',
SEARCH_HOUSE_LOAD_DATA_SUCCESS: 'SEARCH_HOUSE_LOAD_DATA_SUCCESS',
SEARCH_HOUSE_LOAD_DATA_FAIL: 'SEARCH_HOUSE_LOAD_DATA_FAIL',
SEARCH_HOUSE_WILL_UNMOUNT: 'SEARCH_HOUSE_WILL_UNMOUNT',
复制代码
export function loadData(params, currentPage, errorCallBack) {
return dispatch => {
dispatch({ type: currentPage == 1 ? Types.SEARCH_HOUSE_LOAD_DATA : Types.SEARCH_HOUSE_LOAD_MORE_DATA });
setTimeout(() => {
Network
.my_request({
apiPath: ApiPath.SEARCH,
apiMethod: 'searchByPage',
apiVersion: '1.0',
params: {
...params,
pageNo: currentPage,
pageSize: 10
}
})
.then(response => {
const tmpResponse = AppUtil.makeSureObject(response);
const hasMoreData = currentPage < tmpResponse.totalPages;
const houseList = AppUtil.makeSureArray(tmpResponse.resultList);
dispatch({
type: Types.SEARCH_HOUSE_LOAD_DATA_SUCCESS,
currentPage: ++currentPage,
houseList,
hasMoreData,
});
})
.catch(error => {
if (errorCallBack) errorCallBack(error.message);
const action = { type: Types.SEARCH_HOUSE_LOAD_DATA_FAIL };
if (currentPage == 1) {
action.houseList = []
action.currentPage = 1;
};
dispatch(action);
});
}, 300);
}
}
复制代码
// 默认的state
const defaultState = {
houseList: [],
headerIsRefreshing: false,
footerRefreshState: FooterRefreshState.Idle,
currentPage: 1,
}
export function searchHouseReducer(state = defaultState, action) {
switch (action.type) {
case Types.SEARCH_HOUSE_LOAD_DATA: {
return {
...state,
headerIsRefreshing: true
}
}
case Types.SEARCH_HOUSE_LOAD_MORE_DATA: {
return {
...state,
footerRefreshState: FooterRefreshState.Refreshing,
}
}
case Types.SEARCH_HOUSE_LOAD_DATA_FAIL: {
return {
...state,
headerIsRefreshing: false,
footerRefreshState: FooterRefreshState.Failure,
houseList: action.houseList ? action.houseList : state.houseList,
currentPage: action.currentPage,
}
}
case Types.SEARCH_HOUSE_LOAD_DATA_SUCCESS: {
const houseList = action.currentPage <= 2 ? action.houseList : state.houseList.concat(action.houseList);
let footerRefreshState = FooterRefreshState.Idle;
if (AppUtil.isEmptyArray(houseList)) {
footerRefreshState = FooterRefreshState.EmptyData;
} else if (!action.hasMoreData) {
footerRefreshState = FooterRefreshState.NoMoreData;
}
return {
...state,
houseList,
currentPage: action.currentPage,
headerIsRefreshing: false,
footerRefreshState,
}
}
default:
return state;
}
}
复制代码
class SearchHousePage extends Component {
// ...代码省略
componentDidMount() {
this._loadData(true);
}
componentWillUnmount() {
NavigationUtil.dispatch(Types.SEARCH_HOUSE_WILL_UNMOUNT);
}
_loadData(isRefresh) {
const { loadData, searchHouse } = this.props;
const currentPage = isRefresh ? 1 : searchHouse.currentPage;
loadData(this.filterParams, currentPage, error => Toaster.autoDisapperShow(error));
}
render() {
const { home, searchHouse } = this.props;
return (
<View style={styles.container} ref='container'>
<RefreshFlatList
ref='flatList'
style={{ marginTop: AppUtil.fullNavigationBarHeight + 44 }}
showsHorizontalScrollIndicator={false}
data={searchHouse.houseList}
keyExtractor={item => `${item.id}`}
renderItem={({ item, index }) => this._renderHouseCell(item, index)}
headerIsRefreshing={searchHouse.headerIsRefreshing}
footerRefreshState={searchHouse.footerRefreshState}
onHeaderRefresh={() => this._loadData(true)}
onFooterRefresh={() => this._loadData(false)}
footerRefreshComponent={footerRefreshState => this.footerRefreshComponent(footerRefreshState, searchHouse.houseList)}
/>
<NavigationBar
navBarStyle={{ position: 'absolute' }}
backOrCloseHandler={() => NavigationUtil.goBack()}
title='搜房'
/>
<SearchFilterMenu
style={styles.filterMenu}
cityId={`${home.cityId}`}
subwayData={home.subwayData}
containerRef={this.refs.container}
filterMenuType={this.params.filterMenuType}
onChangeParameters={() => this._loadData(true)}
onUpdateParameters={({ nativeEvent: { filterParams } }) => {
this.filterParams = {
...this.filterParams,
...filterParams,
};
}}
/>
</View>
);
}
}
const mapStateToProps = state => ({ home: state.home, searchHouse: state.searchHouse });
const mapDispatchToProps = dispatch => ({
loadData: (params, currentPage, errorCallBack) =>
dispatch(loadData(params, currentPage, errorCallBack)),
});
export default connect(mapStateToProps, mapDispatchToProps)(SearchHousePage);
复制代码
从上面的代码使用了一个connect
函数,connect
链接React组件与Redux store,链接操做会返回一个新的与Redux store链接的组件类,而且链接操做不会改变原来的组件类。
mapStateToProps
中订阅了home节点和searchHouse节点,该页面主要使用searchHouse节点,那订阅home节点是用来方便组件间通讯,这样页面进行网络请求所需的cityId,就不须要从前以页面传入,也不须要从缓存中读取。
列表的刷新状态由headerIsRefreshing
和footerRefreshState
进行管理。
redux已经帮咱们完成了页面的状态管理,再总结一下Redux须要注意的点:
到这里,咱们实现了列表的下拉刷新、加载更多以及如何使用redux,还差一个筛选栏和子菜单页面的开发,这里涉及到React Native与原生之间的通讯,我会在React Native实现一个带筛选功能的搜房列表(3)中分享下如何进行React Native与原生的桥接开发。
另外上面提供的代码均是从项目当中截取的,若是须要查看完整代码的话,在代码传送门--NNHybrid中。
上述相关代码路径:
redux文件夹: /NNHybridRN/redux
SearchHousePage: /NNHybridRN/sections/searchHouse/SearchHousePage.js
复制代码
参考资料: