如何提高此 React Native 代码的性能?

How can I improve performance of this React Native code?

我正在开发我的 React Native 应用程序,我有一个屏幕,我正在使用 RN Cameraroll 并获取所有图像并允许用户 select n 图像数量。我正在标记由用户 select 编辑的图像并按顺序标记它们。当我编写屏幕代码时,我使用了 Android 模拟器,它工作得很好。但是当我在我的三星 s7 设备(我作为测试设备使用的老一代 phone 之一)中测试它时,它没有按预期工作。因此视图得到更新并呈现条件视图,在其中模糊图像并按顺序显示该图像的编号。这是它的 gif 文件,

如您所见,它正在呈现数字,但速度很慢。现在这在这个设备上特别慢。我有几个其他 Android 设备,我已经测试过这个屏幕,但它们是更新的设备和更快的设备,所以它们工作得更好。不像我的模拟器那样流畅,但它们工作正常。所以存在性能问题,但我想如果我知道如何为这个设备修复它,所有设备的性能都会得到修复。

我正在使用 useState() 挂钩将图像路径的顺序存储在数组中。所以这是我的代码,


const SCREEN_WIDTH = Math.floor(Dimensions.get('window').width);
const PADDING = Math.floor(SCREEN_WIDTH * 0.015);
const RENDER_IMAGES_PER_ROW = SCREEN_WIDTH >= 500 ? 5 : 4;
const IMAGES_ROWS = 8;
const FETCH_IMAGES_PER_REQUEST = RENDER_IMAGES_PER_ROW * IMAGES_ROWS;
const WH = Math.floor((SCREEN_WIDTH - PADDING) / RENDER_IMAGES_PER_ROW);
const IMAGE_WIDTH = WH;
const IMAGE_HEIGHT = WH;

    const initialImagesInOrder: string[] = [];
    const [imagesInOrder, setImagesInOrder] = useState(initialImagesInOrder);
    const [media, setMedia] = useState([]);
    const [mediaToRender, setMediaToRender] = useState([]);
    const [after, setAfter] = useState('');
    const [checkedForMedia, setCheckedForMedia] = useState(false);
    const [initialRequest, setInitialRequest] = useState(true);
    const [hasNextPage, setHasNextPage] = useState(true);
    const [videoURI, setVideoURI] = useState('');


    const hasAndroidPermission = async () => {
        const permission =
            PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE;

        const hasPermission = await PermissionsAndroid.check(permission);
        if (hasPermission) {
            return true;
        }

        const status = await PermissionsAndroid.request(permission);
        return status === 'granted';
    };

    const permissionCheck = async () => {
        if (Platform.OS === 'android' && !(await hasAndroidPermission())) {
            return;
        }
    };

    const getLocalMedia = (_after: string | null) => {
        if (hasNextPage) {
            let params = {
                first: FETCH_IMAGES_PER_REQUEST,
                assetType: props.mediaType,
                include: ['playableDuration'],
            };

            if (_after !== null) {
                // @ts-ignore
                params.after = _after;
            }

            // @ts-ignore
            CameraRoll.getPhotos(params)
                .then(r => {
                    // @ts-ignore
                    const newImages = media.concat(r.edges);

                    setCheckedForMedia(true);
                    setInitialRequest(false);

                    // @ts-ignore
                    setMedia(newImages);

                    // @ts-ignore
                    setAfter(r.page_info.end_cursor);
                    // @ts-ignore
                    setHasNextPage(r.page_info.has_next_page);
                })
                .catch(error => {
                    modalData.modalText = `Unable to load media!`;
                    modalData.modalIsVisible = true;
                    setModalData(modalData);
                    
                });
        }
    };

    useEffect(() => {
        permissionCheck()
            .then(() => {
                setCheckedForMedia(false);
                setMedia([]);
                setInitialRequest(true);
                setHasNextPage(true);
                setImagesInOrder(initialImagesInOrder);
                setVideoURI('');
            })
            .catch(error => {
                modalData.modalText = `Something went wrong! Please try again.`;
                modalData.modalIsVisible = true;
                setModalData(modalData);

            });
    }, [props]);

    useEffect(() => {
        if (
            media.length === 0 &&
            !checkedForMedia &&
            initialRequest &&
            hasNextPage
        ) {
            getLocalMedia(null);
        }
    }, [media, checkedForMedia, initialRequest, hasNextPage]);

    useEffect(() => {
        setMediaToRender(
            media.reduce((all, one, i) => {
                const ch = Math.floor(i / RENDER_IMAGES_PER_ROW);
                // @ts-ignore
                all[ch] = [].concat(all[ch] || [], one);
                return all;
            }, []),
        );
    }, [media]);


    const handleImage = (imageURI: string) => {
        if (imagesInOrder.includes(imageURI)) {
            setImagesInOrder(imagesInOrder.filter(i => i !== imageURI));
        } else {
            if (
                props.maxImagesPerPost !== undefined &&
                props.maxImagesPerPost !== null &&
                imagesInOrder.length < props.maxImagesPerPost
            ) {
                setImagesInOrder([...imagesInOrder, imageURI]);
            }
        }
    };

    // @ts-ignore
    const renderItem = ({ item }) => {
        if (props.mediaType === 'Photos') {
            return renderImages(item);
        } else {
            return renderVideos(item);
        }
    };

    const renderImageBackground = ({
        uri,
        data,
        seconds,
    }: {
        uri: string;
        data: any | null;
        seconds: number | null;
    }) => (
        <ImageBackground
            style={styles.imageBackgroundStyle}
            source={{ uri }}
            resizeMode="cover"
            key={uri}>
            {data !== undefined && data !== null && (
                <View style={styles.imageBackgroundChild}>
                    <View style={styles.imageSelectedView}>{data}</View>
                </View>
            )}
            {data == null && seconds !== null && (
                <View
                    style={
                        props.maxVideoLengthInSeconds !== undefined &&
                        seconds > props.maxVideoLengthInSeconds
                            ? {
                                    ...styles.secondsViewForVideo,
                                    ...styles.videoDisabled,
                              }
                            : {
                                    ...styles.secondsViewForVideo,
                              }
                    }>
                    <Text
                        style={
                            props.maxVideoLengthInSeconds !== undefined &&
                            seconds > props.maxVideoLengthInSeconds
                                ? styles.disabledSecondsTextForVideo
                                : styles.secondsTextForVideo
                        }>
                        {formatSecondsForVideo(seconds.toString())}
                    </Text>
                </View>
            )}
        </ImageBackground>
    );

    const renderImages = (item: any) => (
        <View style={styles.mediaView}>
            {
                // @ts-ignore
                item.map(i => {
                    const uri = i.node.image.uri;
                    const data = imagesInOrder.includes(uri) ? (
                        <Text style={styles.imageSelectedText}>
                            {imagesInOrder.indexOf(uri) + 1}
                        </Text>
                    ) : null;
                    const seconds = null;

                    return (
                        <View
                            style={styles.mediaSubView}
                            key={Helper.generateRandomKey()}>
                            <TouchableOpacity
                                onPress={() => {
                                    handleImage(uri);
                                }}>
                                {renderImageBackground({
                                    uri,
                                    data,
                                    seconds,
                                })}
                            </TouchableOpacity>
                        </View>
                    );
                })
            }
        </View>
    );

    const handleMedia = () => {
        if (checkedForMedia && media.length === 0) {
            return (
                <View style={[styles.containerForNoImage]}>
                    <Text style={[styles.text]}>
                        No {props.mediaType === 'Photos' ? `photo` : `video`}{' '}
                        found!
                    </Text>
                </View>
            );
        }

        return (
            <FlatList
                data={mediaToRender}
                keyExtractor={(item, index) => index.toString()}
                // @ts-ignore
                renderItem={renderItem}
                initialNumToRender={5}
                maxToRenderPerBatch={10}
                windowSize={10}
                onEndReachedThreshold={0.5}
                onEndReached={() => {
                    getLocalMedia(after);
                }}
            />
        );
    };

所以我的逻辑很简单。从状态数组中添加或删除图像,如果其路径存在于数组中则更新渲染图像。如果路径存在,获取索引并更新视图。但我不确定为什么它在我的测试设备中的执行速度不够快! order 数组不是太大而应该引起问题。正如您在 gif 中看到的那样,模糊视图会立即呈现,但中间的文本不会随之呈现。

此外,我只分享了处理图像内容的代码块!如果我遗漏了什么,请告诉我,我会更新代码!此外,当我滚动图像或首次渲染此视图时,它会缓慢加载图像!有什么建议可以更快地加载图像吗?它们本地存储在设备上,不会从远程服务器获取!

在此先感谢您的帮助!

所以我能够在 azundo 的建议的帮助下修复它。这是我的愚蠢错误。每次渲染图像视图时我都使用随机键!但是在使用Image URI之后,它解决了渲染慢的问题,现在效果好多了。

正如@azundo 所说,

Can you try using a the uri as the key for the images instead of a random one? Could be causing some unnecessary thrashing on the rendering side if it thinks those are all new elements every render. If uri isn't unique the generate the random key once and store it alongside each image instead of creating a new one each render.

谢谢@azundo!也感谢@docmurloc 的时间和帮助。