如何在React Native裏面使用 FlatList 採用 Hooks 函數?

Hooks 是javascript的函數, 他讓我們可以使用state不需要寫一個class

因此,Hooks 被用到了 react 函數組件,而不是class組件

請記住,Hooks文檔中概述了我們需要遵循的規則,應該做的和不應該做的。文檔地址:https://reactjs.org/docs/hooks-rules.html

  • 不要在循環或嵌套函數中調用Hook,更不要說在條件邏輯中了
  • 在您的React函數開始的最高點處執行Call Hooks。

理由

想象一下序列的工作原理,或者也許您可以考慮數組如何工作以更好地理解它。

對於React中的渲染過程,將爲每個渲染以完全相同的順序調用Hook。如果弄亂了渲染中調用的順序,則可能導致數據不一致。並且這種情況是禁止的。

如果您有興趣確保可以在其他地方找到一些相關的文章,我們可能會在本文中通過unpacking how Hooks work主題。

首先…鉤子用例! 規則,還記得嗎?

1.在功能組件而不是類中使用它們…

2.在函數開始的最頂部調用它們。

const ItemList = props => {   const [limit, setLimit] = useState(5);
   const [page, setPage] = useState(1);
   const [clientData, setClientData] = useState([]);
   const [serverData, serverDataLoaded] = useState([]);
   const [pending_process, setPending_process] = useState(true);
   const [loadmore, setLoadmore] = useState(false);
}

讓我們設置一個虛擬數據庫,如下所示: 這就是我們將用來複制從API調用返回的響應的功能。

 const itemList = [  {id: '1', name: 'Item!'},
   {id: '2', name: 'Item@'},
   {id: '3', name: 'Item#'},
   {id: '4', name: 'Item$'},
   {id: '5', name: 'Item%'},
   {id: '6', name: 'Item^'},
   {id: '7', name: 'Item&'},
   {id: '8', name: 'Item*'},
   {id: '9', name: 'Item('},
   {id: '10', name: 'Item)'},
   {id: '11', name: 'Item!!'},
   {id: '12', name: 'Item@@'},
   {id: '13', name: 'Item##'},
   {id: '14', name: 'Item$$'},
   {id: '15', name: 'Item%%'},
   {id: '16', name: 'Item^^'},
   {id: '17', name: 'Item&&'},
   {id: '18', name: 'Item**'},
   {id: '19', name: 'Item(('},
   {id: '20', name: 'Item))'}
]; 

我們爲API調用添加了一些模擬功能,如下所示:

// delay for 1500ms to simulate a API request time.
// we start with page = 1.
// itemList is a constants dataset as show above.
const ApiRequest = async thePage => {
  await setTimeout(() => {}, 1500);
  return itemList.slice((thePage - 1) * limit, thePage * limit);
};
const requestToServer = async thePage => {
   let data = await ApiRequest(thePage); 
   serverDataLoaded(data);
};

然後,我們將API響應數據設置爲狀態變量“ serverData”。

接下來,我們繼續進行“ useEffect”掛鉤。

“ useEffect”掛鉤具有某些生命週期過程的相同目的,即componentDidMount,componentDidUpdate和componentWillUnmount,它在特定功能組件中執行其副作用。

我們首先調用D-I-Y“ API”,以使用“ useEffect”鉤子獲取數據。我們只希望Hook運行一次即可在初始渲染時獲取第一組數據。因此,我們將空數組傳遞給第二個參數。

useEffect(() => {
	requestToServer(page); // page => 1
}, []);

我們可以在一個功能組件中使用多個useEffect Hooks。

我們添加它來管理serverData狀態的副作用。 看到,第二個參數現在由[serverData]提供,我們期望對serverData狀態更改產生一些副作用:我們將它們與clientData合併以顯示。

useEffect(() => {
   setClientData([...clientData, ...serverData]);
}, [serverData]);

再次,我們建立另一個Hook來觀察頁面狀態。頁面更改後,我們再次從“服務器”請求數據。

useEffect(() => {
   requestToServer(page);
}, [page]);

然後,“ handleLoadMore”函數用於處理頁面更改。

const handleLoadMore = () => {
   setPage(page + 1);
};

其次……FlatList用例! 基本FlatList的外觀如下:

<FlatList
   data={clientData}
   renderItem={renderRow}
   onEndReached={handleLoadMore}
   onEndReachedThreshold={0.1}
   onRefresh={() => onRefresh()}
/>

在添加邏輯之後,我們具有類似於以下內容: 這裏沒有其他要注意的事情:

  • onEndReachedThreshold | number
  • onEndReached | function
  • onRefresh | function
  • pending_process | state
  • loadmore | state

在這裏插入圖片描述

第三……動畫API! 請參見下面的Animated.FlatList的框架:

import { Animated } from 'react-native';
const ItemList = props => {
  ...
  const [scrollY, setScrollY] = useState(new Animated.Value(0));
  const [op, setOp] = useState(
     scrollY.interpolate({
        inputRange: [0, 50, 100, 150],
        outputRange: [1, 0.5, 0.25, 0]
     }));
   ...
   const renderRow = ({item, index, separators}) => {
      return (
        <Animated.View style={{opacity: op}}>
           <ListItem
              style={{ 
                height: ITEM_HEIGHT, 
                backgroundColor: Constants.COLOR.GOLD
              }}>
             <Text style={{color: 'white'}}>{item.name}</Text
           </ListItem>
         </Animated.View>
      )
   }
 
   return (
      <Animated.FlatList
         onScroll={Animated.event(
          [
           {
             nativeEvent: {contentOffset: {y: scrollY}},
           },
          ],
          {
            useNativeDriver: true,
            listener: handleScroll
          },
         )}
       />
   );
}

scrollY:新的Animated.Value(0) 被定義爲用作動畫實例,起始值爲0。

scrollY.interpolate({inputRange,outputRange}) 是相對於我們期望的滾動Y軸[inputRange]的不透明度[outputRange]的變化樣式。

我們將添加邏輯。以及新動畫值的一些計算。最終內容將類似於……

import React, {useState, useEffect, useRef} from 'react';

import {View, Animated, Dimensions, Text} from 'react-native';
import {ListItem} from 'native-base';
import {_} from 'lodash';

import generalStyles from '../../generalStyles';
import Constants from '../../Constants';

const {height} = Dimensions.get('window');
const ITEM_HEIGHT = height * 0.2;

const itemList = [
  {id: '1', name: 'Item!'},
  {id: '2', name: 'Item@'},
  {id: '3', name: 'Item#'},
  {id: '4', name: 'Item$'},
  {id: '5', name: 'Item%'},
  {id: '6', name: 'Item^'},
  {id: '7', name: 'Item&'},
  {id: '8', name: 'Item*'},
  {id: '9', name: 'Item('},
  {id: '10', name: 'Item)'},
  {id: '11', name: 'Item!!'},
  {id: '12', name: 'Item@@'},
  {id: '13', name: 'Item##'},
  {id: '14', name: 'Item$$'},
  {id: '15', name: 'Item%%'},
  {id: '16', name: 'Item^^'},
  {id: '17', name: 'Item&&'},
  {id: '18', name: 'Item**'},
  {id: '19', name: 'Item(('},
  {id: '20', name: 'Item))'},
];

const ItemListDemo = props => {
  const flatListRef = useRef(null);

  const [limit, setLimit] = useState(5);
  const [page, setPage] = useState(1);
  const [clientData, setClientData] = useState([]);
  const [serverData, serverDataLoaded] = useState([]);
  const [pending_process, setPending_process] = useState(true);
  const [loadmore, setLoadmore] = useState(false);

  const [refresh, setRefresh] = useState(false);
  const [fadingIndex, setFadingIndex] = useState(0);
  const [scrollDirection, setScrollDirection] = useState(false);
  const [offsetY, setOffsetY] = useState(0);

  const [scrollY, setScrollY] = useState(new Animated.Value(0));
  const [op, setOp] = useState(
    scrollY.interpolate({
      inputRange: [0, 50, 100, 150],
      outputRange: [1, 0.5, 0.25, 0],
    }),
  );

  const ApiRequest = async thePage => {
    await setTimeout(() => {}, 1500);
    return itemList.slice((thePage - 1) * limit, thePage * limit);
  };

  const requestToServer = async thePage => {
    let data = await ApiRequest(thePage);
    console.log('data', data);
    serverDataLoaded(data);
  };

  useEffect(() => {
    console.log('requestToServer');
    requestToServer(page);
  }, []);

  useEffect(() => {
    console.log('obtained serverData', serverData);
    if (serverData.length > 0) {
      setRefresh(false);
      setClientData([...clientData, ...serverData]);
      setLoadmore(serverData.length == limit ? true : false);
      setPending_process(false);
    } else {
      setLoadmore(false);
    }
  }, [serverData]);

  useEffect(() => {
    console.log('load more with page', page);
    if (serverData.length == limit || page == 1) {
      setPending_process(true);
      requestToServer(page);
    }
  }, [page]);

  const handleLoadMore = () => {
    console.log('loadmore', loadmore);
    console.log('pending_process', pending_process);
    if (loadmore && !pending_process) {
      setPage(page + 1);
    }
  };

  const onRefresh = () => {
    setClientData([]);
    setPage(1);
    setRefresh(true);
    setPending_process(false);
  };

  const renderRow = ({item, index, separators}) => {
    return (
      <Animated.View
        style={
          index < fadingIndex && scrollDirection == 'down' ? {opacity: op} : {}
        }>
        <ListItem
          style={{
            height: ITEM_HEIGHT,
            backgroundColor: Constants.COLOR.GOLD,
          }}>
          <Text style={{color: 'white'}}>{item.name}</Text>
        </ListItem>
      </Animated.View>
    );
  };

  const handleScroll = event => {
    let {contentSize, contentOffset} = event.nativeEvent;
    let h = ITEM_HEIGHT;
    let reachingIndex = Math.ceil(contentOffset.y / h);

    setOffsetY(contentOffset.y);
    setScrollDirection(contentOffset.y > offsetY ? 'down' : 'up');

    let in_range = [0, h / 3, (h / 3) * 2, h];
    let out_range = [1, 0.5, 0.25, 0];
    let out_range_new = [];
    let in_range_new = [];

    let d = h / 3;
    for (i = 0; i <= reachingIndex; i++) {
      out_range_new = _.concat(out_range_new, out_range);
      _.each(in_range, (val, index) => {
        let n = in_range_new.length + 1;
        let next_num = in_range[0] + (n - 1) * d;
        in_range_new.push(next_num);
      });
    }

    console.log('out_range_new', out_range_new);
    console.log('in_range_new', in_range_new);

    setOp(
      scrollY.interpolate({
        inputRange: in_range_new,
        outputRange: out_range_new,
      }),
    );
    setFadingIndex(reachingIndex);
  };

  const getItemLayout = (data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  });

  return (
    <View style={[generalStyles.container, generalStyles.bg_white]}>
      <Animated.FlatList
        onScroll={Animated.event(
          [
            {
              nativeEvent: {contentOffset: {y: scrollY}},
            },
          ],
          {
            useNativeDriver: true,
            listener: handleScroll,
          },
        )}
        getItemLayout={getItemLayout}
        refreshing={refresh}
        data={clientData}
        renderItem={renderRow}
        onEndReached={handleLoadMore}
        onEndReachedThreshold={0.1}
        onRefresh={() => onRefresh()}
      />
    </View>
  );
};

export default ItemListDemo;

在這裏插入圖片描述

是的!最後……我們將爲FlatList進行一些優化配置!

*1. Use getItemLayout*

*2. Apply keyExtractor props to FlatList*

*3. Avoid feeding anonymous function to “renderItem” props*

4. Use useMemo Hooks to replace “shouldComponentUpdate”

import React, {useState, useEffect, useMemo} from 'react';
import { Dimensions } from 'react-native';

const {height} = Dimensions.get('window');
const ITEM_HEIGHT = height * 0.25;
const ItemListDemo = props => {
  
   const keyExtractor = (item, index) => item.id;
   const getItemLayout = (data, index) => ({
      length: ITEM_HEIGHT,
      offset: ITEM_HEIGHT * index,
      index
   });
   return useMemo(() => {
       return (
           <FlatList
             keyExtractor={keyExtractor}
             data={clientData}
             renderItem={renderRow}
             getItemLayout={getItemLayout}
           />
       );
     }, [clientData, fadingIndex]);
   };
}

結論:

我們從本文的第一部分學到了React Hooks的基礎知識,並且在隨後的部分中我們還獲得了一些使用useState和useEffect Hooks的動手編程經驗。 其次,我們使用按需數據提取邏輯從頭開始運行FlatList,然後使用Animated API來控制列表項的不透明樣式。 最後,我們還對React Native中的FlatList進行了一些性能配置!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章