React Native TabView + FlatList爬坑记

关注公众号前端方程式,更多前端小干货等着你喔!公众号会不按期分享前端技术,天天进步一点点,与你们相伴成长前端

最近接了一个简单列表页面的需求,小列表硬是爬大坑,给你们介绍一下本次爬坑的艰辛历程。java

需求很简单,如上图所示,一个信息介绍,加一个左右滑动的列表。主要的工做量都在于这个TAB与左右滑动的交互上,问题不大,直接上插件,一番查找下,很快就找到了一个react-native-tab-view的组件,效果彻底符合要求。react

安装react-native-tab-viewnpm

好的,开始安装,一顿npm install与npm link。segmentfault

yarn add react-native-tab-view
yarn add react-native-reanimated react-native-gesture-handler
react-native link react-native-reanimated
react-native link react-native-gesture-handler

再抄上一个demo。react-native

import * as React from 'react';
import { View, StyleSheet, Dimensions } from 'react-native';
import { TabView, SceneMap } from 'react-native-tab-view';
​
​
const FirstRoute = () => (
 <View style={[styles.scene, { backgroundColor: '#ff4081' }]} />
);
​
​
const SecondRoute = () => (
 <View style={[styles.scene, { backgroundColor: '#673ab7' }]} />
);
​
​
const initialLayout = { width: Dimensions.get('window').width };
​
​
export default function TabViewExample() {
 const [index, setIndex] = React.useState(0);
 const [routes] = React.useState([
 { key: 'first', title: 'First' },
 { key: 'second', title: 'Second' },
 ]);
​
​
 const renderScene = SceneMap({
 first: FirstRoute,
 second: SecondRoute,
 });
​
​
 return (
 <TabView
 navigationState={{ index, routes }}
 renderScene={renderScene}
 onIndexChange={setIndex}
 initialLayout={initialLayout}
 />
 );
}
​
​
const styles = StyleSheet.create({
 scene: {
 flex: 1,
 },
});

哦豁,并无成功运行,红色错误警告。仔细查看文档,文档中有一个大大的 IMPORTANT 提示,react-native-gesture-handler 库在安卓上有一个额外的配置。原来是须要在 MainActivity.java 额外加一个函数。ide

package com.swmansion.gesturehandler.react.example;
​
​
import com.facebook.react.ReactActivity;
+ import com.facebook.react.ReactActivityDelegate;
+ import com.facebook.react.ReactRootView;
+ import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
public class MainActivity extends ReactActivity {
​
​
 @Override
 protected String getMainComponentName() {
 return "Example";
 }
+  @Override
+  protected ReactActivityDelegate createReactActivityDelegate() {
+    return new ReactActivityDelegate(this, getMainComponentName()) {
+      @Override
+      protected ReactRootView createRootView() {
+       return new RNGestureHandlerEnabledRootView(MainActivity.this);
+      }
+    };
+  }
}

火速加上,再稍微调整一下UI,打完收工,回家睡觉!函数

回家是不可能回家的,这仅仅是完成了TabView的功能,每一个列表还须要上拉加载分页的功能呢。性能

分页功能测试

通常来讲,这种的列表不可能就只有几条数据,分页功能是必不可少的,而RN中想要作一个高性能的列表咱们能够选择 FlatList 来实现。

FlatList 基于 VirtualizedList 能够实现一个高性能的列表,其在渲染区域外的元素状态将再也不保留,自始至终仅保留少许元素渲染在页面中,从而保证超长列表时不会由于元素过多致使页面卡顿甚至是奔溃的状况发生。

而且 FlatList 同时支持下拉刷新与上拉加载更多功能,是实现超长列表的不二选择,使用也很简单。

<FlatList
 refreshing={true}
 style={styles.wrap}
 data={data}
 renderItem={renderItem}
 keyExtractor={item => '' + item.id}
 ListEmptyComponent={empty}
 onEndReached={onEndReached}
 onEndReachedThreshold={0.5}
/>

那么问题来了,实际使用中发现,加载一屏数据后,滚动条自动滚动到底部,致使整个页面跳动明显,并且滚动到顶部也明显不符合要求。

缘由很容易猜到,多半是由于 FlatList 高度不肯定致使的,简单验证一下,去除 TabView ,仅使用 FlatList 列表,结果显示表现正常,而我这边使用 FlatList 是使用 flex: 1; 指定了高度的,那么这是为何呢?

那再猜一下,多半是由于 TabView 组件在渲染底部列表的时候在中间增长了其余容器且没有设置高度致使的,仔细查看react-native-tab-view源代码,发现 src/SceneMap.tsx 中,被用于渲染页面的函数 renderScene 调用。

class SceneComponent<
 T extends { component: React.ComponentType<any> }
> extends React.PureComponent<T> {
 render() {
 const { component, ...rest } = this.props;
 return React.createElement(component, rest);
 }
}
​
​
export default function SceneMap<T extends any>(scenes: {
 [key: string]: React.ComponentType<T>;
}) {
 return ({ route, jumpTo, position }: T) => (
 <SceneComponent
 key={route.key}
 component={scenes[route.key]}
 route={route}
 jumpTo={jumpTo}
 position={position}
 />
 );
}

此处 renderScene 会在实际页面上层包裹上包括 <SceneComponent> 以及 React.createElement 在内的两层容器,从而致使页面高度丢失,最终致使 FlatList 滚动异常。

仔细查看 TabView 中的 renderScene 可使用另一种写法。

renderScene = ({ route }: any) => {
 const { activeIndex, tabs } = this.state;
 switch (route.key) {
 case 'all':
 return (
 <List
 data={tabs[0].data}
 onReachBottom={this.onReachBottom}
 isVisible={activeIndex === 0}
 />
 );
 case 'earning':
 return (
 <List
 data={tabs[1].data}
 onReachBottom={this.onReachBottom}
 isVisible={activeIndex === 1}
 />
 );
 case 'spending':
 return (
 <List
 data={tabs[2].data}
 onReachBottom={this.onReachBottom}
 isVisible={activeIndex === 2}
 />
 );
 }
};

使用该方法,能够避免上述两层容器的生成,直接填充指定的组件,天然也就不会出现高度丢失的问题了。

仔细对比一下两种方式生成的元素,能够很明显发现,初始写法多了两层容器,分别就是<SceneComponent>以及 React.createElement 生成的 <all>。再验证一下效果,滚动确实正常了,不会出现滚动条自动滚动到底部的问题,完美!

so!你觉得就结束了吗?并无!使用该方式,第一页的列表确实表现正常,可是第二页以及第三页的列表竟然没法滚动,经测试发现,页面初始加载的时候第2、三页初始就没有触发 onEndReached 致使后续加载没法触发,缘由不想再纠结了,暴力解决一下,既然没有触发,那就手动给他触发一下能够了。

// 列表组件中
​
​
const [initialized, setInitialized] = useState(false);
const { onReachBottom, isVisible = false } = props;
// isVisible = 当前列表的index === TabView的activeIndex
​
useEffect(() => {
 if (isVisible && !initialized) {
 onReachBottom();
 setInitialized(true);
 }
}, [isVisible, initialized]);

到此,全部的功能都完成了,完美,又是提早下班的一天!

本文首发于本人公众号,前端方程式,分享与关注前端技术,欢迎关注~~

前端方程式

相关文章
相关标签/搜索