代码之家  ›  专栏  ›  技术社区  ›  yiksanchan

如何避免每次选择/取消选择行时重新呈现本机平面列表

  •  0
  • yiksanchan  · 技术社区  · 5 年前

    Reproducible demo code

    我面临的问题是,每次单击一行时,都会重新呈现所有行,这可以从日志中看出(例如“呈现项id”)=卡梅隆.阮@example.com网站,selected=false“)。我想避免对未更改的行进行重新渲染,因为重新渲染可能会很昂贵(在我想加载非常大的图像或列表非常长的情况下),但我没有弄清楚如何进行。我两个都试过了 <MomoizedItem /> <MemoizedItem2 /> 利用 React.memo <Item /> 和其中一个一起看效果。我也试着用 onClickCallBack 结束 onClick ,但也无济于事。

    我在用吗 反应备忘录 React.useCallBack

    如果代码链接过期,请粘贴以下代码:

    import React, { memo, useEffect, useState } from "react";
    import { SafeAreaView, FlatList, StyleSheet } from "react-native";
    import Constants from "expo-constants";
    import { Set } from "immutable";
    import { Button, ListItem } from "react-native-elements";
    import axios from "axios";
    
    const Item = ({ id, title, avatarUrl, selected, onClick }) => {
      console.log(`rendering item id=${id}, selected=${selected}`);
      return (
        <ListItem
          title={title}
          leftAvatar={{ source: { uri: avatarUrl } }}
          containerStyle={[
            styles.item,
            { backgroundColor: selected ? "#6e3b6e" : "#f9c2ff" }
          ]}
          underlayColor="transparent"
          onPress={() => onClick(id)}
        />
      );
    };
    function itemEq(prevItem, nextItem) {
      return prevItem.id === nextItem.id && prevItem.selected === nextItem.selected;
    }
    
    // Does not make a difference, every time a row is clicked, all rows are re-rendered
    const MemoizedItem = memo(Item);
    // Make some difference but the behavior looks very weird. Try click around and see the log
    const MemoizedItem2 = memo(Item, itemEq);
    
    const Items = ({ data, selectedItems, onClick }) => {
      console.log("rendering items");
      // Replace <Item /> with <MemoizedItem /> or <MemoizedItem2 /> to see effect
      const _renderItem = ({ item }) => (
        <Item
          id={item.email}
          title={`${item.name.title} ${item.name.first} ${item.name.last}`}
          avatarUrl={item.picture.thumbnail}
          selected={selectedItems.has(item.email)}
          onClick={onClick}
        />
      );
      return (
        <FlatList
          data={data}
          renderItem={_renderItem}
          keyExtractor={item => item.email}
          extraData={selectedItems}
        />
      );
    };
    
    const App = () => {
      const [items, setItems] = useState([]);
      const [selectedItems, setSelectedItems] = useState(Set());
    
      useEffect(() => {
        const fetchData = async () => {
          console.log("fetching data");
          // Read 5 random users back
          // Each user is like this:
          // {
          //   "gender":"male",
          //     "name":{
          //   "title":"Mr",
          //       "first":"Harley",
          //       "last":"Zhang"
          // },
          //   "location":{
          //   "street":{
          //     "number":6470,
          //         "name":"Buckleys Road"
          //   },
          //   "city":"Palmerston North",
          //       "state":"Manawatu-Wanganui",
          //       "country":"New Zealand",
          //       "postcode":90911,
          //       "coordinates":{
          //     "latitude":"66.2907",
          //         "longitude":"-18.0881"
          //   },
          //   "timezone":{
          //     "offset":"+8:00",
          //         "description":"Beijing, Perth, Singapore, Hong Kong"
          //   }
          // },
          //   "email":"harley.zhang@example.com",
          //     "login":{
          //   "uuid":"6fda195e-3e63-476c-84d0-7c577c7b74f9",
          //       "username":"smallbear541",
          //       "password":"daisy1",
          //       "salt":"p6AmByUq",
          //       "md5":"0358f2385a9936369adc89b9233f037b",
          //       "sha1":"8decc817cf32ca6e58814502bb3e54152208c5b5",
          //       "sha256":"96ff7627348250646edd31238504271840a0cb6aaac293782f7eec1a6f884c07"
          // },
          //   "dob":{
          //   "date":"1987-12-07T13:00:15.244Z",
          //       "age":33
          // },
          //   "registered":{
          //   "date":"2008-01-23T19:33:01.672Z",
          //       "age":12
          // },
          //   "phone":"(474)-743-9612",
          //     "cell":"(539)-021-1315",
          //     "id":{
          //   "name":"",
          //       "value":null
          // },
          //   "picture":{
          //   "large":"https://randomuser.me/api/portraits/men/49.jpg",
          //       "medium":"https://randomuser.me/api/portraits/med/men/49.jpg",
          //       "thumbnail":"https://randomuser.me/api/portraits/thumb/men/49.jpg"
          // },
          //   "nat":"NZ"
          // }
          const results = await axios("https://randomuser.me/api/?results=5");
          setItems(results.data.results);
        };
        fetchData();
      }, []);
    
      const onClick = id => {
        const newSelectedItems = selectedItems.has(id)
            ? selectedItems.delete(id)
            : selectedItems.add(id);
    
        console.log(`selected items=${JSON.stringify(selectedItems, null, 2)}`);
        console.log(
            `new selected items=${JSON.stringify(newSelectedItems, null, 2)}`
        );
        setSelectedItems(newSelectedItems);
      }
    
      // Does not help
      const onClickUseCallBack = React.useCallback(
        id => {
          const newSelectedItems = selectedItems.has(id)
            ? selectedItems.delete(id)
            : selectedItems.add(id);
    
          console.log(`selected items=${JSON.stringify(selectedItems, null, 2)}`);
          console.log(
            `new selected items=${JSON.stringify(newSelectedItems, null, 2)}`
          );
          setSelectedItems(newSelectedItems);
        },
        [selectedItems]
      );
    
      return (
        <SafeAreaView style={styles.container}>
          <Items data={items} selectedItems={selectedItems} onClick={onClick} />
          <Button
            title="Print"
            onPress={() => console.log(`Printing selected items ${selectedItems}`)}
          />
        </SafeAreaView>
      );
    };
    
    export default App;
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        marginTop: Constants.statusBarHeight,
        marginHorizontal: 16
      },
      item: {
        backgroundColor: "#f9c2ff",
        padding: 20,
        marginVertical: 8
      }
    });
    

    博览会包.json

    {
      "main": "node_modules/expo/AppEntry.js",
      "scripts": {
        "start": "expo start",
        "android": "expo start --android",
        "ios": "expo start --ios",
        "web": "expo start --web",
        "eject": "expo eject"
      },
      "dependencies": {
        "axios": "^0.19.2",
        "expo": "~36.0.0",
        "immutable": "^4.0.0-rc.12",
        "react": "~16.9.0",
        "react-dom": "~16.9.0",
        "react-native": "https://github.com/expo/react-native/archive/sdk-36.0.0.tar.gz",
        "react-native-elements": "^1.2.7",
        "react-native-web": "~0.11.7"
      },
      "devDependencies": {
        "@babel/core": "^7.0.0",
        "babel-preset-expo": "~8.0.0"
      },
      "private": true
    }
    
    0 回复  |  直到 5 年前
        1
  •  2
  •   Mohamed Ramrami    5 年前

    MomoizedItem + onClickUseCallBack 这是个好的开始。

    重新渲染是因为 已实施。看,你有 selectedItems 作为第二个参数 useCallback ,每次选择/取消选择项目时, 选择编辑项 onClickUseCallBack 要创建一个,然后传递给每一个项目,其中 memo 并使每个项目重新呈现。

    选择编辑项 使用回调 ,则为了避免出现过时的状态值(由于闭包的工作方式),请使用状态设置器的函数形式来生成新的值。

      const onClickUseCallBack = React.useCallback(
        id => {
          setSelectedItems((selectedItems) => {
            const newSelectedItems = selectedItems.has(id)
              ? selectedItems.delete(id)
              : selectedItems.add(id);
    
            return newSelectedItems
          });
        },
        []
      );
    

    演示

    https://snack.expo.io/HJXkV!Q48

    完整代码

    import React, { memo, useEffect, useState } from "react";
    import { SafeAreaView, FlatList, StyleSheet } from "react-native";
    import Constants from "expo-constants";
    import { Set } from "immutable";
    import { Button, ListItem } from "react-native-elements";
    import axios from "axios";
    
    const Item = ({ id, title, avatarUrl, selected, onClick }) => {
      console.log(`rendering item id=${id}, selected=${selected}`);
      return (
        <ListItem
          title={title}
          leftAvatar={{ source: { uri: avatarUrl } }}
          containerStyle={[
            styles.item,
            { backgroundColor: selected ? "#6e3b6e" : "#f9c2ff" }
          ]}
          underlayColor="transparent"
          onPress={() => onClick(id)}
        />
      );
    };
    function itemEq(prevItem, nextItem) {
      return prevItem.id === nextItem.id && prevItem.selected === nextItem.selected;
    }
    
    // Does not make a difference, every time a row is clicked, all rows are re-rendered
    const MemoizedItem = memo(Item);
    // Make some difference but the behavior looks very weird. Try click around and see the log
    const MemoizedItem2 = memo(Item, itemEq);
    
    const Items = ({ data, selectedItems, onClick }) => {
      console.log("rendering items");
      // Replace <Item /> with <MemoizedItem /> or <MemoizedItem2 /> to see effect
      const _renderItem = ({ item }) => (
        <MemoizedItem
          id={item.email}
          title={`${item.name.title} ${item.name.first} ${item.name.last}`}
          avatarUrl={item.picture.thumbnail}
          selected={selectedItems.has(item.email)}
          onClick={onClick}
        />
      );
      return (
        <FlatList
          data={data}
          renderItem={_renderItem}
          keyExtractor={item => item.email}
          extraData={selectedItems}
        />
      );
    };
    
    const App = () => {
      const [items, setItems] = useState([]);
      const [selectedItems, setSelectedItems] = useState(Set());
    
      useEffect(() => {
        const fetchData = async () => {
          console.log("fetching data");
          // Read 5 random users back
          // Each user is like this:
          // {
          //   "gender":"male",
          //     "name":{
          //   "title":"Mr",
          //       "first":"Harley",
          //       "last":"Zhang"
          // },
          //   "location":{
          //   "street":{
          //     "number":6470,
          //         "name":"Buckleys Road"
          //   },
          //   "city":"Palmerston North",
          //       "state":"Manawatu-Wanganui",
          //       "country":"New Zealand",
          //       "postcode":90911,
          //       "coordinates":{
          //     "latitude":"66.2907",
          //         "longitude":"-18.0881"
          //   },
          //   "timezone":{
          //     "offset":"+8:00",
          //         "description":"Beijing, Perth, Singapore, Hong Kong"
          //   }
          // },
          //   "email":"harley.zhang@example.com",
          //     "login":{
          //   "uuid":"6fda195e-3e63-476c-84d0-7c577c7b74f9",
          //       "username":"smallbear541",
          //       "password":"daisy1",
          //       "salt":"p6AmByUq",
          //       "md5":"0358f2385a9936369adc89b9233f037b",
          //       "sha1":"8decc817cf32ca6e58814502bb3e54152208c5b5",
          //       "sha256":"96ff7627348250646edd31238504271840a0cb6aaac293782f7eec1a6f884c07"
          // },
          //   "dob":{
          //   "date":"1987-12-07T13:00:15.244Z",
          //       "age":33
          // },
          //   "registered":{
          //   "date":"2008-01-23T19:33:01.672Z",
          //       "age":12
          // },
          //   "phone":"(474)-743-9612",
          //     "cell":"(539)-021-1315",
          //     "id":{
          //   "name":"",
          //       "value":null
          // },
          //   "picture":{
          //   "large":"https://randomuser.me/api/portraits/men/49.jpg",
          //       "medium":"https://randomuser.me/api/portraits/med/men/49.jpg",
          //       "thumbnail":"https://randomuser.me/api/portraits/thumb/men/49.jpg"
          // },
          //   "nat":"NZ"
          // }
          const results = await axios("https://randomuser.me/api/?results=5");
          setItems(results.data.results);
        };
        fetchData();
      }, []);
    
      // Does not help
      const onClickUseCallBack = React.useCallback(
        id => {
          setSelectedItems((selectedItems) => {
            const newSelectedItems = selectedItems.has(id)
              ? selectedItems.delete(id)
              : selectedItems.add(id);
    
            return newSelectedItems
          });
        },
        []
      );
    
      return (
        <SafeAreaView style={styles.container}>
          <Items data={items} selectedItems={selectedItems} onClick={onClickUseCallBack} />
          <Button
            title="Print"
            onPress={() => console.log(`Printing selected items ${selectedItems}`)}
          />
        </SafeAreaView>
      );
    };
    
    export default App;
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        marginTop: Constants.statusBarHeight,
        marginHorizontal: 16
      },
      item: {
        backgroundColor: "#f9c2ff",
        padding: 20,
        marginVertical: 8
      }
    });