乐趣区

关于前端:React-Native-TabView-FlatList爬坑记

关注公众号 前端方程式,更多前端小干货等着你喔!公众号会不定期分享前端技术,每天提高一点点,与大家相伴成长

最近接了一个简略列表页面的需要,小列表硬是爬大坑,给大家介绍一下本次爬坑的艰苦历程。

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

装置 react-native-tab-view

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

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。

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 额定加一个函数。

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!你认为就完结了吗?并没有!应用该形式,第一页的列表的确体现失常,然而第二页以及第三页的列表竟然无奈滚动,经测试发现,页面初始加载的时候第二、三页初始就没有触发 onEndReached 导致后续加载无奈触发,起因不想再纠结了,暴力解决一下,既然没有触发,那就手动给他触发一下能够了。

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

到此,所有的性能都实现了,完满,又是提前上班的一天!

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

退出移动版