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]);

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

本文首發於本人公衆號,前端方程式,分享與關注前端技術,歡迎關注~~

前端方程式

相關文章
相關標籤/搜索