React Native实现一个带筛选功能的搜房列表(2)
原文鏈接React Native實現一個帶篩選功能的搜房列表(2)
在上一篇中,我們實現了一個下拉刷新和上拉加載更多的列表,那根據一般的開發步驟,接著應該就是進行網絡請求,在網絡請求之后更新列表數據和列表的刷新狀態。
這篇文章會向大家介紹一下Redux的基本概念以及在頁面中如何使用Redux進行狀態管理。
文章中的代碼都來自代碼傳送門--NNHybrid。開始之前,我們先看一下最終實現的效果
Redux概念
首先先簡單介紹一下Redux的一些概念。Redux是JavaScript狀態容器,提供可預測化的狀態管理,其工作流程如下:
整個工作流程為:
Store
Store是存儲state的容器,負責提供所有的狀態。整個應用只能有一個Store,這么做的目的是為了讓組件之間的通信更加簡單。
在沒有Store的情況下,組件之間需要通信就比較麻煩,如果一個父組件要將狀態傳遞到子組件,就需要通過props一層一層往下傳,一個子組件的狀態發生改變并且要讓父組件知道,則必須暴露一個事件出去才能通信。這就使得組件之間通信依賴于組件的層次結構。此時如果有兩個平級的節點想要通信,就需要通過它們的父組件進行中轉。 有了這個全局的Store之后,所有的組件變成了和Store進行通信。這樣組件之間通信就會變少,當Store發生變化,對應的組件也能拿到相關的數據。當組件內部有時間觸發Store的變化時,更新Store即可。這也就是所謂的單向數據流過程。
Store的職責如下:
- 維持應用的state;
- 提供getState()方法獲取state;
- 提供dispatch(action)方法更新state;
- 通過subscribe(listener)注冊監聽器;
- 通過subscribe(listener)返回的函數注銷監聽器。
Action
當我們想要更改store中的state時,我們便需要使用Action。Action是Store數據的唯一來源,每一次修改state便要發起一次Action。
Action可以理解為是一個Javascript對象。其內部必須包含一個type字段來表示將要執行的動作,除了 type字段外,Action的結構完全由自己決定。多數情況下,type字段會被定義成字符串常量。
Action舉例:
{type: Types.SEARCH_HOUSE_LOAD_DATA_SUCCESS,currentPage: ++currentPage,houseList,hasMoreData, } 復制代碼Action創建函數
Action創建函數就是生成action的方法。“action” 和 “action 創建函數” 這兩個概念很容易混在一起,使用時最好注意區分。
Action創建函數舉例:
export function init(storeName) {return dispatch => {dispatch({ type: Types.HOUSE_DETAIL_INIT, storeName });} } 復制代碼Reducer
Store收到Action以后,必須給出一個新的State,這樣View才會發生變化。 這種State的計算過程就叫做Reducer。Reducer是一個純函數,它只接受Action和當前State作為參數,返回一個新的State。
由于Reducer是一個純函數,所以我們不能在reducer里執行以下操作:
- 修改傳入的參數;
- 執行有副作用的操作;
- 調用非純函數;
- 不要修改state;
- 遇到未知的action時,一定要返回舊的state;
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。一般情況下,針對一個頁面可以設置一個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); } 復制代碼SearchHousePage使用Redux
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', 復制代碼Action創建函數
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);} } 復制代碼創建reducer
// 默認的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'><RefreshFlatListref='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)}/><NavigationBarnavBarStyle={{ position: 'absolute' }}backOrCloseHandler={() => NavigationUtil.goBack()}title='搜房'/><SearchFilterMenustyle={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應用只有一個單一的Store。當需要拆分數據處理邏輯時,你應該使用拆分與合并reducer而不是創建多個Store;
- redux一個特點是:狀態共享,所有的狀態都放在一個Store中,任何組件都可以訂閱Store中的數據,但是不建議組件訂閱過多Store中的節點;
- 不要將所有的State都適合放在Store中,這樣會讓Store變得非常龐大;
到這里,我們實現了列表的下拉刷新、加載更多以及如何使用redux,還差一個篩選欄和子菜單頁面的開發,這里涉及到React Native與原生之間的通信,我會在React Native實現一個帶篩選功能的搜房列表(3)中分享下如何進行React Native與原生的橋接開發。
另外上面提供的代碼均是從項目當中截取的,如果需要查看完整代碼的話,在代碼傳送門--NNHybrid中。
上述相關代碼路徑:
redux文件夾: /NNHybridRN/reduxSearchHousePage: /NNHybridRN/sections/searchHouse/SearchHousePage.js 復制代碼參考資料:
Redux 中文文檔
轉載于:https://juejin.im/post/5cf88bee6fb9a07ebd48c68f
總結
以上是生活随笔為你收集整理的React Native实现一个带筛选功能的搜房列表(2)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安装oracle后,电脑变卡变慢的解决办
- 下一篇: spring-boot-devtools