背景
下拉刷新上拉加载性能在挪动端无论是 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 等等一样的性能。
// 设置自定义 header
useLayoutEffect(() => {
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 踩坑一步一个脚印,社区给不了的,本人想方法解决。