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常用的屬性方法
- data: ?Array 爲了簡化起見,data屬性目前只支持普通數組。
- renderItem: (info: {item: ItemT, index: number}) => ?React.Element 根據行數據data渲染每一行的組件
- onRefresh?: ?() => void 如果設置了此選項,則會在列表頭部添加一個標準的RefreshControl控件,以便實現“下拉刷新”的功能。同時你需要正確設置refreshing屬性。
- refreshing?: ?boolean 在等待加載新數據時將此屬性設爲true,列表就會顯示出一個正在加載的符號。
- onEndReachedThreshold?: ?number 決定當距離內容最底部還有多遠時觸發onEndReached回調。注意此參數是一個比值而非像素單位。比如,0.5表示距離內容最底部的距離爲當前列表可見長度的一半時觸發。
- onEndReached?: ?(info: {distanceFromEnd: number}) => void 當列表被滾動到距離內容最底部不足onEndReachedThreshold的距離時調用。
- ItemSeparatorComponent?: ?ReactClass 行與行之間的分隔線組件。不會出現在第一行之前和最後一行之後。
- keyExtractor: (item: ItemT, index: number) => string 此函數用於爲給定的item生成一個不重複的key。Key的作用是使React能夠區分同類元素的不同個體,以便在刷新時能夠確定其變化的位置,減少重新渲染的開銷。若不指定此函數,則默認抽取item.key作爲key值。若item.key也不存在,則使用數組下標。
- ListHeaderComponent?: ?ReactClass 頭部組件
- ListFooterComponent?: ?ReactClass 尾部組件
- ListEmptyComponent?: ?ReactClass | React.Element 列表爲空時渲染該組件。可以是React Component, 也可以是一個render函數, 或者渲染好的element。
extraData?: any 如果有除data以外的數據用在列表中(不論是用在renderItem還是Header或者Footer中),請在此屬性中指定。同時此數據在修改時也需要先修改其引用地址(比如先複製到一個新的Object或者數組中),然後再修改其值,否則界面很可能不會刷新。
注:當然還有其他的屬性方法,請自行查閱文檔FlatList
2.封裝一個上拉加載,下拉刷新的組件
- 先定義一些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} 當前實例