如何在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进行了一些性能配置!

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