背景
下拉刷新上拉加载性能在挪动端无论是App还是小程序都是十分高频应用的组件,最近在开发RN的时候,本想着这个轮子应该很成熟了,于是跑遍了github
都找不到一个自我感觉很完满的刷新组件,而且star数高的组件很多都是几年前保护的,说到这个,不得不吐槽一下,RN的生态在国内真的是太窄了,遇到略微小众一点的问题,国内某搜索引擎能找到答案的概率为50%,某歌的概率为80%,并且反复率十分高,始终认为RN会因为react
的光环,社区应该会十分欠缺,后果令我悲观。
挣扎了一番之后,决定还是用RN自带的组件--flatList
,略微封装了一层,在Tab路由页应用没有问题,然而在详情页应用的时候,遇到了第一次加载和切换tab时,flatList刷新时候的indicator没有显示的bug,找遍了全网,只有国外友人遇到过,rn的issue也看到过,然而是18年左右的,被人关了,并且没有解决方案,惟一一个遇到跟我截然不同的人,他的解决方案是曲线救国,每次刷新的时候让列表滚回到顶部(因为indicator没有显示,其实是列表滚到底部把那个菊花给笼罩了),不能承受这种解决方案。
开发环境
RN版本:0.64.0
UI组件库:react native elements
组件类型:函数式(hooks)
导航版本(react native navigaton):5.X
尝试计划
不管怎么设置list的高度也好,外层高度也好,写死高度和flex设置为1全都试过,全都没有### 背景
下拉刷新上拉加载性能在挪动端无论是App还是小程序都是十分高频应用的组件,最近在开发RN的时候,本想着这个轮子应该很成熟了,于是跑遍了github
都找不到一个自我感觉很完满的刷新组件,而且star数高的组件很多都是几年前保护的,说到这个,不得不吐槽一下,RN的生态在国内真的是太窄了,遇到略微小众一点的问题,国内某搜索引擎能找到答案的概率为50%,某歌的概率为80%,并且反复率十分高,始终认为RN会因为react
的光环,社区应该会十分欠缺,后果令我悲观。
挣扎了一番之后,决定还是用RN自带的组件--flatList
,略微封装了一层,在Tab路由页应用没有问题,然而在详情页应用的时候,遇到了第一次加载和切换tab时,flatList刷新时候的indicator没有显示的bug,找遍了全网,只有国外友人遇到过,rn的issue也看到过,然而是18年左右的,被人关了,并且没有解决方案,惟一一个遇到跟我截然不同的人,他的解决方案是曲线救国,每次刷新的时候让列表滚回到顶部(因为indicator没有显示,其实是列表滚到底部把那个菊花给笼罩了),不能承受这种解决方案。
预期后果
理论后果
开发环境
RN版本:0.64.0
UI组件库:react native elements
组件类型:函数式(hooks)
导航版本(react native navigaton):5.X
尝试计划
不管怎么设置list的高度也好,外层高度也好,写死高度和flex设置为1全都试过,全都没有用,然而我在设置refreshing为true的中央加了定时器,提早去刷新,就能失常显示菊花,所以我猜想是其余dom还没挂载渲染完,flatList就去刷新了,这个时候外层元素高度还没确定,所以列表滚到最底下,造成了菊花隐没的景象,然而useEffect会在组件渲染到屏幕之后执行,讲道理不应该会产生这种bug
解决方案
在无数遍的调试下,我发现把react native elements组件库的Header组件移除后,第一次进入页面的菊花失常显示了,然而切换页面还是没有显示。因为我想让list组件在平安区域显示,所以我的flatList包裹了一层SafeAreaView,款式设置了flex:1,当我尝试把他移除后,应用View代替了它,当初后果如我预期显示。
父组件:
<View style={common.container}> <ButtonGroup onPress={updateIndex} selectedIndex={selectedIndex} buttons={['文件', '流程核心']} containerStyle={styles.buttonGroup} textStyle={styles.buttonGroupText} /> {selectedIndex === 0 ? <ProjectFileList /> : <ProjectWorkflow />}</View>
子组件:
<View style={styles.container}> <SearchBar containerStyle={styles.searchBarContainer} inputStyle={styles.searchInput} inputContainerStyle={styles.searchInputContainer} lightTheme={true} placeholder="搜寻" /> <RefreshableList loadMore={loadMore} data={dataList} renderItem={renderItem} refreshing={refreshing} onRefresh={handleRefresh} onEndReached={handleLoadMore} keyExtractor={item => item.resId} /></View>
自定义Header
启用了react native elements的Header后,开始寻找代替的Header计划,最初还是决定用react native navigation提供的api实现。
screenOptions配置页面导航的默认参数
配置导航的全局对立款式:
<Stack.Navigator initialRouteName="Login" screenOptions={({ navigation }) => { return { headerStyle: { backgroundColor: colors.primary }, headerTitleStyle: { color: '#FFFF' }, headerBackTitleStyle: { color: '#FFFF' }, headerBackTitleVisible: false, headerBackImage: () => ( <TouchableOpacity onPress={navigation.goBack}> <Ionicons name="arrow-back" size={24} color={'#fff'} /> </TouchableOpacity> ), headerLeftContainerStyle: { paddingLeft: 10 }, headerRightContainerStyle: { paddingRight: 10 } }; }} > {// 页面的若干配置...}</Stack.Navigator>
NavigationContainer能够承受一个theme参数,承受主题款式,在路由外面就能应用useTheme
拿到全局款式。
<NavigationContainer theme={MyTheme}> {// 若干配置...}</NavigationContainer>
MyTheme.js
import { DefaultTheme } from '@react-navigation/native';const MyTheme = { ...DefaultTheme, colors: { ...DefaultTheme.colors, primary: '#0784ff', bgGray: '#efeff3', text: '#262626', infoText: '#909399' }};export default MyTheme;
那么如果有一些导航须要一些自定义的按钮事件须要跟页面联动,怎么解决呢?
其实,navigation中有setOptions办法,就是跟你在配置页面路由时配置Header Title、backTitle等等一样的性能。
// 设置自定义headeruseLayoutEffect(() => { navigation.setOptions({ headerRight: () => ( <TouchableOpacity onPress={() => drawerRef.current.toggleSideMenu()}> <Ionicons name="menu" size={24} color={'#fff'} /> </TouchableOpacity> ), headerTitle: () => ( <Text style={common.headerTitleText}>{route.params.name}</Text> ) });}, [navigation]);
flatList组件
我把通用的办法封装了,须要在页面实现的办法通过props传递给组件 ,并且修复了flatLIst组件上拉加载可能遇到的bug。
import React, { useEffect, useState } from 'react';import { View, FlatList, ActivityIndicator, Text } from 'react-native';import styles from './styles';import Empty from '../Empty';const RefreshableList = props => { const { setEndReachedCalled, loadMore } = props; const renderFooter = () => { return loadMore ? ( <View style={styles.footer}> <ActivityIndicator /> <Text>正在加载更多数据...</Text> </View> ) : ( <></> ); }; return ( <FlatList {...props} contentContainerStyle={props.data.length ? null : { flexGrow: 1 }} onEndReachedThreshold={0.2} onMomentumScrollBegin={() => { // 有些页面遇到第一次加载就触发loadMore的状况,如首页我的项目核心,遇到此状况须要传递setEndReachedCalled函数管制触发条件 setEndReachedCalled ? setEndReachedCalled(false) : null; }} ListEmptyComponent={() => <Empty />} ItemSeparatorComponent={ // eslint-disable-next-line no-undef Platform.OS !== 'android' && (({ highlighted }) => ( <View style={[styles.separator, highlighted && { marginLeft: 0 }]} /> )) } ListFooterComponent={renderFooter} /> );};export default RefreshableList;
总结
RN踩坑一步一个脚印,社区给不了的,本人想方法解决。