React Native achieved with a screening function room search list (2)

Original link React Native achieved with a screening function room search list (2)

In the previous , we realized a drop-down list and refresh the LAC upload more, that according to the general development steps, then it should be is the network request, update data and refresh the status list after list of network requests.

This article will introduce the basic concepts and how to use Redux Redux state management in the page.

Article code from the code portal --NNHybrid .

Redux concept

First, briefly explain some of the concepts of Redux. JavaScript is Redux state of the container, providing predictable of state management, which works as follows:

The entire workflow:


1966287-2aa38686844df417
reduxProcess
  1. View Store to subscribe to the state;
  2. View operation (clicking on a button or performing a network request View), the Action issued;
  3. Store automatically call Reducer, and two arguments (Old State and Action), Reducer returns the new State, if there Middleware, Store and Old State Action will be passed to the Middleware, Middleware calls Reducer and then return the new State;
  4. State if there is a change, Store listener function is called to update the View;

Store

Store is a storage state of the container, it is responsible for providing all of the state. The entire application can only have one Store, The goal is to enable communication between components easier.

1966287-d27ef09a1967014c
reduxCommunication

在没有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'>
                <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,就不需要从前以页面传入,也不需要从缓存中读取。

列表的刷新状态由headerIsRefreshingfooterRefreshState进行管理。

综上

redux已经帮我们完成了页面的状态管理,再总结一下Redux需要注意的点:

  • Redux应用只有一个单一的Store。当需要拆分数据处理逻辑时,你应该使用拆分与合并reducer而不是创建多个Store;
  • a redux features are: a shared state, all states are in a Store, you can subscribe to any of the components in the data Store, but it is not recommended to subscribe to too many components Store in the node;
  • Do not have fit in all the State Store, which would allow the Store becomes very large;

Here, we realized the drop-down list refreshes to load more and how to use redux, short of a bar screening and development of the sub-menu page, here involves communication between React Native and Native, I will implement a bring React Native SouFun list filtering feature (3) under the Share how to bridge the development React native and native.

Another source provided above are taken from the project which, if you need to see the complete code words in the code portal --NNHybrid in.

The correlation code path:

redux文件夹: /NNHybridRN/redux

SearchHousePage: /NNHybridRN/sections/searchHouse/SearchHousePage.js

References:

Redux Chinese Documents

Reproduced in: https: //www.jianshu.com/p/20632354b241

Guess you like

Origin blog.csdn.net/weixin_34247032/article/details/91152401
Recommended