1. 程式人生 > >react-native之上拉載入,下拉重新整理元件封裝

react-native之上拉載入,下拉重新整理元件封裝

react-native 自定義封裝重新整理元件

幾個月沒寫部落格了,最近一直在寫react 和react-native,前幾天剛發了一版基於react-native混合開發的App,這幾天趕快總結下。

寫過java的同學,再去學習react和react-native就會比較容易上手,並且會有一種似曾相識的感覺,不錯,今天我要總結的重新整理功能就和Android原生的實現很類似,在Android原生當中,可以說是家喻戶曉了,在react-native中實現也是一樣的簡單,在rn的升級中,FlatList是listView的升級版,就好比如RecyclerView是listView的升級版一樣。OK, 廢話不多話說。

目錄

1.FlatList常用的屬性方法

2.封裝一個上拉載入,下拉重新整理的元件

1.FlatList常用的屬性方法

  1. data: ?Array 為了簡化起見,data屬性目前只支援普通陣列。
  2. renderItem: (info: {item: ItemT, index: number}) => ?React.Element 根據行資料data渲染每一行的元件
  3. onRefresh?: ?() => void 如果設定了此選項,則會在列表頭部新增一個標準的RefreshControl控制元件,以便實現“下拉重新整理”的功能。同時你需要正確設定refreshing屬性。
  4. refreshing?: ?boolean 在等待載入新資料時將此屬性設為true,列表就會顯示出一個正在載入的符號。
  5. onEndReachedThreshold?: ?number 決定當距離內容最底部還有多遠時觸發onEndReached回撥。注意此引數是一個比值而非畫素單位。比如,0.5表示距離內容最底部的距離為當前列表可見長度的一半時觸發。
  6. onEndReached?: ?(info: {distanceFromEnd: number}) => void 當列表被滾動到距離內容最底部不足onEndReachedThreshold的距離時呼叫。
  7. ItemSeparatorComponent?: ?ReactClass 行與行之間的分隔線元件。不會出現在第一行之前和最後一行之後。
  8. keyExtractor: (item: ItemT, index: number) => string 此函式用於為給定的item生成一個不重複的key。Key的作用是使React能夠區分同類元素的不同個體,以便在重新整理時能夠確定其變化的位置,減少重新渲染的開銷。若不指定此函式,則預設抽取item.key作為key值。若item.key也不存在,則使用陣列下標。
  9. ListHeaderComponent?: ?ReactClass 頭部元件
  10. ListFooterComponent?: ?ReactClass 尾部元件
  11. ListEmptyComponent?: ?ReactClass | React.Element 列表為空時渲染該元件。可以是React Component, 也可以是一個render函式, 或者渲染好的element。
  12. extraData?: any 如果有除data以外的資料用在列表中(不論是用在renderItem還是Header或者Footer中),請在此屬性中指定。同時此資料在修改時也需要先修改其引用地址(比如先複製到一個新的Object或者陣列中),然後再修改其值,否則介面很可能不會重新整理。

    注:當然還有其他的屬性方法,請自行查閱文件FlatList

2.封裝一個上拉載入,下拉重新整理的元件

  1. 先定義一些propTypes,利於元件的擴充套件使用
static propTypes = {
        renderItem: PropTypes.func.isRequired,    /* 渲染行的方法 */
        ItemSeparatorComponent: PropTypes.func,   /* 渲染行間隔的方法 */
        keyExtractor: PropTypes.func,             /* 返回每行key的方法 */
        ListFooterComponent: PropTypes.func,      /* 渲染列表底部的方法 */
        ListHeaderComponent: PropTypes.func,      /* 渲染列表頭部的方法 */
        listUrl: PropTypes.string.isRequired,     /* 獲取列表的url */
        fetchMethod: PropTypes.string,            /* 獲取列表的請求方式(預設為get) */
        fetchParams: PropTypes.object,            /* 獲取列表的請求引數 */
        pageSize: PropTypes.number,               /* 每頁行數(預設20條) */
        formatData: PropTypes.func,               /* 返回列表陣列的方法 */
        _ref: PropTypes.func,                     /* 獲取元件例項屬性 */
        auto: PropTypes.bool,                     /* 是否在載入時自動查詢(預設自動) */
        showFooterBoo: PropTypes.bool,            /* 是否顯示腳 */
        ref: PropTypes.func,                      /* 獲取元件例項 */
    };

2.初始化當前狀態state

constructor(props) {
        super(props);

        this._fetchUrl = props.listUrl;
        this._fetchParams = props.fetchParams;

        this.state = {
            list: [],            /* 列表資料 */
            pageNum: 1,          /* 當前頁數 */
            count: 0,            /* 總條數 */
            refreshing: false,   /* 是否正在載入 */
            isEnd: false,        /* 是否載入完 */
            isNotList: false,    /* 是否未查詢到資料 */
            isError: false,      /* 是否查詢失敗 */
        }
    }

3.FlatList元件屬性封裝

  • 渲染前準備
const { list, refreshing } = this.state;

        const {
            renderItem,
            ItemSeparatorComponent,
            keyExtractor = (...arg) => arg[1],
            ListHeaderComponent,
            extraData = {},
            showFooterBoo,
        } = this.props;
  • FlatList封裝

<FlatList
                data={list}
                renderItem={renderItem}
                onRefresh={this.onRefresh}
                refreshing={refreshing}
                onEndReachedThreshold={0.2}
                onEndReached={this.onEndReached}
                ItemSeparatorComponent={ItemSeparatorComponent}
                keyExtractor={keyExtractor}
                ListHeaderComponent={this.renderListHeader}
                ListFooterComponent={showFooterBoo ? this.renderListFooter : null}
                ListEmptyComponent={this.renderListEmpty}
                ref={ref => this._listRef = ref}
                extraData={extraData}
            />

4.data={list} 中list資料來源於引用元件處的屬性

5.renderItem={renderItem} 中renderItem來源於引用元件的Item佈局屬性

6.onRefresh={this.onRefresh} 下拉重新整理佈局如下:

/**
     * 下拉重新整理
     * @return {void}
     */
    onRefresh = () => {
        this.setState({
            isEnd: false,
        }, () => {
            this.getList();
        });
    }
/**
     * 獲取列表
     * @return {void}
     */
    getList = (isRefresh = true) => {
        const {
            $fetch,
            fetchMethod = 'get',
            pageSize = 20,
            formatData = ({data}) => ({
                list: data.records,
                count: data.total,
            }),
            fetchCatch = () => {},
        } = this.props;

        const { list, pageNum, refreshing, isEnd } = this.state;

        if((refreshing || isEnd) && !isRefresh) return;

        const fetchPageNum = isRefresh ? 1 : pageNum;
        let fetchPromise;

        if(fetchMethod.toLowerCase() === 'get') {
            fetchPromise = $fetch.get(this._fetchUrl, {
                size: pageSize,
                current: fetchPageNum,
                ...this._fetchParams,
            });
        } else if(fetchMethod.toLowerCase() === 'post') {
            fetchPromise = $fetch.post(`${this._fetchUrl}`, {
                ...this._fetchParams,
            });
        } else {
            throw new Error('method type error! only "get" or "post"');
        }

        this.setState({refreshing: true});
        fetchPromise.then(data => {
            console.log(data);
            if(!data.success) {
                this.setState({ isError: true });
                return;
            }
            const listData = formatData(data);
            const currentList = isRefresh ? listData.list : list.concat(listData.list);
            /* 判斷是否查詢到資料 */
            if(fetchPageNum === 1 && listData.list.length === 0){
                this.setState({
                    isNotList: true
                });
            } else {
                this.setState({
                    isNotList: false
                });
            }

            /* 如果有資料則新增到列表中 */
            if(listData.list.length) {
                this.setState({
                    list: currentList,
                    pageNum: fetchPageNum + 1,
                });
            }
            if(currentList.length >= listData.count) {
                this.setState({
                    isEnd: true
                });
            }
            this.setState({
                count: listData.count,
            });
        }).catch(err => {
            console.log(err.response);
            if(!data.success) {
                this.setState({ isError: true });
            }
            fetchCatch(err);
        }).finally(() => {
            this.setState({refreshing: false});
        });
    }

注:get與post可以根據自己的具體需求進行優化,其中網路請求使用的fetch。

7.refreshing={refreshing} 中refreshing布林值,是用來判斷當前是否是出於重新整理狀態的,當處於請求資料的重新整理狀態時置為true。

8.onEndReachedThreshold={0.2} 當滑動比例為0.2時觸發onEndReached方法;onEndReached={this.onEndReached}上拉載入實現,如下:

/**
     * 上拉載入
     * @return {void}
     */
    onEndReached = () => {
        this.getList(false);
    }

注:this.getList(false);方法同下拉重新整理,引數false代表上拉載入。

9.ItemSeparatorComponent={ItemSeparatorComponent} 自定義一個分割線,如下:

export const ItemSeparator = props => {
    const {
        paddingLeft = px(40),
        backgroundColor = '#fff',
        borderColor = '#eee',
    } = props;
    return (
        <View style={{paddingLeft, backgroundColor}}>
            <View style={{height: 1, backgroundColor: borderColor}}></View>
        </View>
    );
};

10.keyExtractor={keyExtractor} 使用當前資料的id即可,如下:

keyExtractor={item => item.id}

11.ListHeaderComponent={this.renderListHeader} 頭部的渲染,程式碼如下:

/**
     * 渲染列表頭部
     */
    renderListHeader = () => {
        const { count } = this.state;
        const { ListHeaderComponent = () => <View></View> } = this.props;
        return ListHeaderComponent(this.state);
    }

注:佈局根據自己的需求進行定義即可。

12.ListFooterComponent={showFooterBoo ? this.renderListFooter : null} 屬性showFooterBoo布林值用來控制是否顯示腳,腳的佈局定義如下:


    /**
     * 渲染列表底部
     * @return {ReactComponent}
     */
    renderListFooter = () => {
        const { ListFooterComponent = this.defaultListFooter } = this.props;
        const { isEnd, refreshing } = this.state;
        return this.defaultListFooter(isEnd, refreshing);
    }
/**
     * 列表預設底部元件
     * @return {Node}
     */
    defaultListFooter = () => {
        return (
            <FlexView justify="center" align="center" style={styles.listFooterView}>
                {this.defaultListFooterContent()}
            </FlexView>
        );
    }
/**
     * 列表底部顯示內容
     * @return {Node}
     */
    defaultListFooterContent = () => {
        const { isEnd, refreshing, isNotList, isError } = this.state;
        if(isNotList) {
            return (
                <_Text>未查詢到相關資料!</_Text>
            );
        }
        if(isEnd) {
            return [
                <FlexItem style={styles.listFooterLine} key={1}/>,
                <_Text key={2}>沒有更多資料</_Text>,
                <FlexItem style={styles.listFooterLine} key={3}/>,
            ];
        }
        if(refreshing) {
            return (
                <_Text>正在載入...</_Text>
            );
        }
        if(isError) {
            return (
                <_Text>查詢失敗,下拉重新載入!</_Text>
            );
        }
        return (
            <_Text>上拉載入更多</_Text>
        );
    }

注:腳的佈局可以根據自己的專案需求進行更改。

13.ListEmptyComponent={this.renderListEmpty} 空佈局,沒有資料,請求出錯顯示,程式碼如下:

/**
     * 列表為空時顯示的元件
     * @return {ReactComponent}
     */
    renderListEmpty = () => {
        return (
            <View>
                <Text>沒有資料哦!</Text>
            </View>
        );
    }

14.ref={ref => this._listRef = ref} 當前例項

ok,總結就到這裡了,後續我會總結更多的使用元件的~~