RN 上拉下拉刷新的组件封装篇

首先,先上一份实现了 List 上下拉刷新的代码:

import React, { Component } from 'react';
import {
    Platform,
    StyleSheet,
    Text,
    View,
    ListView,
    TouchableHighlight,
    RefreshControl,
    Image,
} from 'react-native';

import MZCKit from 'react-native-mzckit';

const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});

export default class PageContent extends Component<Props> {
    static navigationOptions = ({navigation}) => ({
        title: '最新动态',
        headerStyle: { backgroundColor: '#ff6600', },
        headerTintColor: '#FFFFFF',
        headerTitleStyle: { color: 'white' },
    });

    constructor(props) {
        super(props);

        this.state = ({
            refreshing: true,
            loadMore: false,
            name: this.props.name,
            dataSource: [],
            page:1,
        })
    }

    componentDidMount() {
        this._fetchData();
    }

    _fetchData() {
        var that = this;
        var params = {id:2,page:this.state.page,per_page:20};

        var url = 'http://api.test.com' + '/v2/api.article.list';
        MZCKit.post2(url,params, function (data) {
            if (data.error_code === 0) {
                console.log('aaaaaaaaaaaaaaaaaa=========', data);

                let articles = data.articles;

                that.setState({
                    refreshing: false,
                    loadMore: false,
                    dataSource:that.state.dataSource.concat(articles),
                });
            }
        }, function (err) {
            console.log('nnnnnnnnnnnnnnnnnn', err);
            alert(err)
        });
    }

    _onRefresh() {
        this.setState({
            refreshing: true,
            page: 1,
            dataSource: [],
        }, () => this._fetchData());
    }

    _onEndReached() {
        if (this.state.dataSource.length === 0) return;
        this.setState({
            loadMore: true,
            page: this.state.page + 1,
        }, () => this._fetchData());
    }

    _onPress(rowData) {
        const {navigate} = this.props.navigation;
        navigate('NewsDetail', {
            title: rowData.title,
            href: 'http://api.test.com/article/' + rowData.id
        })
    }

    _renderRow(rowData) {
        return <TouchableHighlight
            underlayColor='#008b8b'
            onPress={() => this._onPress(rowData)}>
            <View style={styles.rowStyle}>
                <Text style={{fontSize: 20, flex: 1}}>{rowData.title}</Text>
            </View>
        </TouchableHighlight>
    }

    render() {
        const FooterView = this.state.loadMore ?
            <View style={styles.footer}>
                <Text style={{fontSize: 16, color: '#777'}}>加载更多...</Text>
            </View> : null;
        return <ListView
            refreshControl={
                <RefreshControl
                    refreshing={this.state.refreshing}
                    onRefresh={this._onRefresh.bind(this)}
                />
            }
            style={[styles.listView]}
            dataSource={ds.cloneWithRows(this.state.dataSource)}
            enableEmptySections={true}
            renderRow={this._renderRow.bind(this)}
            onEndReachedThreshold={10}
            onEndReached={this._onEndReached.bind(this)}
            renderFooter={() => FooterView}
        />
    }
}

const styles = StyleSheet.create({
    listView: {
        backgroundColor: '#eee',
    },
    rowStyle: {
        padding: 10,
        backgroundColor: '#fff',
        flexDirection: 'row',
        justifyContent: 'space-between',
        marginBottom: 1,
    },
    footer: {
        flexDirection: 'column',
        justifyContent: 'center',
        alignItems: 'center',
        height: 40,
    },
});

可以看到这个实现有很多方法。

_fetchData     请求事件
_onRefresh     下拉刷新事件
_onEndReached  上拉刷新事件
_onPress       点击事件
_renderRow     实现 List 的 Row

封装

把通用的东西提取出来,比如

_fetchData     请求
_onPress       点击事件
_renderRow     实现 List 的 Row

其他的刷新事件就封装在组件里面。

最终实现 刷新组件

NewsListPageListRefresh.js

import React, { Component } from 'react';
import {
    Platform,
    StyleSheet,
    Text,
    View,
    ListView,
    RefreshControl,
} from 'react-native';

const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});

const styles = StyleSheet.create({
    footer: {
        flexDirection: 'column',
        justifyContent: 'center',
        alignItems: 'center',
        height: 40,
    },
});

export default class PageContent extends Component<Props> {
    static defaultProps = {
        initPage: 1,
        backgroundColor: '#fff',
        footerView : (
            <View style={styles.footer}>
                <Text style={{fontSize: 16, color: '#777'}}>加载更多...</Text>
            </View>
        ),
        footerViewEnd : (
            <View style={styles.footer}>
                <Text style={{fontSize: 16, color: '#777'}}>没有更多的数据。</Text>
            </View>
        )
    }

    constructor(props) {
        super(props);

        this.state = ({
            refreshing: true,
            loadMore: false,
            dataSource: [],
            page:this.props.initPage,
        })
    }

    componentDidMount() {
        if (this.props.fetchData) {
            this.props.fetchData(this);
        }
    }

    _onRefresh() {
        this.setState({
            refreshing: true,
            page: this.props.initPage,
            dataSource: [],
        }, () => {
            if (this.props.fetchData) {
                this.props.fetchData(this);
            }
        });
    }

    _onEndReached() {
        if (this.state.dataSource.length === 0) return;
        this.setState({
            loadMore: true,
            page: this.state.page + 1,
        }, () => {
            if (this.props.fetchData) {
                this.props.fetchData(this);
            }
        });
    }

    render() {
        const FooterView = this.state.loadMore ?
            this.props.footerView : this.props.footerViewEnd;
        return (
            <ListView
                style={{backgroundColor:this.props.backgroundColor}}
                refreshControl={
                    <RefreshControl
                        refreshing={this.state.refreshing}
                        onRefresh={this._onRefresh.bind(this)}
                    />
                }
                style={[styles.listView]}
                dataSource={ds.cloneWithRows(this.state.dataSource)}
                enableEmptySections={true}
                renderRow={this.props.renderRow.bind(this)}
                onEndReachedThreshold={10}
                onEndReached={this._onEndReached.bind(this)}
                renderFooter={() => FooterView}
            />
        )
    }
}

提供属性:

fetchData(this)         接入请求的方法,this 为组件。
renderRow(rowData)      接入List的Row的方法,rowData 就是当前Row 要使用的数据。
backgroundColor         背景颜色值,初始值为#fff。
initPage                默认页,初始值为1。
footerView              "加载更多..."的组件。
footerViewEnd           "没有更多的数据。"的组件。
loadMore                布尔值,判断是否有更多的加载。
refreshing              布尔值,判断是否加载中。

使用该组件

import React, { Component } from 'react';
import {
    Platform,
    StyleSheet,
    Text,
    View,
    TouchableHighlight,
    SafeAreaView,
} from 'react-native';

import MZCKit from 'react-native-mzckit';

import ListRefresh from './NewsListPageListRefresh';

export default class PageContent extends Component<Props> {
    static navigationOptions = ({navigation}) => ({
        title: '最新动态',
        headerStyle: { backgroundColor: '#ff6600', },
        headerTintColor: '#FFFFFF',
        headerTitleStyle: { color: 'white' },
    });

    _fetchData(that) {
        var params = {id:2,page:that.state.page,per_page:20};

        var url = 'http://api.test.com' + '/v2/api.article.list';
        MZCKit.post2(url,params, function (data) {
            if (data.error_code === 0) {
                console.log('aaaaaaaaaaaaaaaaaa=========', data);

                let articles = data.articles;

                that.setState({
                    refreshing: false,
                    loadMore: false,
                    dataSource:that.state.dataSource.concat(articles),
                });
            }
        }, function (err) {
            console.log('nnnnnnnnnnnnnnnnnn', err);
            alert(err)
        });
    }

    _onPress(rowData) {
        const {navigate} = this.props.navigation;
        navigate('NewsDetail', {
            title: rowData.title,
            href: 'http://api.test.com/article/' + rowData.id
        })
    }


    _renderRow(rowData) {
        return <TouchableHighlight
            underlayColor='#008b8b'
            onPress={() => this._onPress(rowData)}>
            <View style={styles.rowStyle}>
                <Text style={{fontSize: 20, flex: 1,color:'#cc9660'}}>{rowData.title}</Text>
            </View>
        </TouchableHighlight>
    }

    render() {
        return <SafeAreaView>
            <ListRefresh backgroundColor='#ccff00'
                renderRow={this._renderRow.bind(this)}
                fetchData={this._fetchData.bind(this)}
                />
            </SafeAreaView>
    }
}

const styles = StyleSheet.create({
    rowStyle: {
        padding: 10,
        backgroundColor: '#fff',
        flexDirection: 'row',
        justifyContent: 'space-between',
        marginBottom: 1,
    },
});