изменение состояния не вызывает повторной визуализации в функциональном компоненте

#reactjs #react-native #react-hooks

#reactjs #react-native #реагирующие крючки

Вопрос:

В приложении React Native 0.63.2 есть функциональный компонент DisplayImages для представления загруженных изображений. Когда порядок изображений был изменен в состоянии Imgs , DisplayImages ожидается повторная визуализация. Вот код:

 import DisplayImages from "./DisplayImages";

export default function Itie({navigation}) { //<<==this is the parent component which calls child DisplayImages
    const [imgs, setImgs] = useState(); //<<==image array to display

      return(
         <MyAccordion 
                            title={"Image"} 
                            absPosition={false} 
                            screenSize={{width:screen_width, height:((imgs amp;amp; imgs.length>9) ? screen_width (screen_width/3)*(Math.ceil(((imgs.length-9)%3)/3)):screen_width)}} 
                            ref= {refImg}
                        >
                            <DisplayImages pics={imgs} deleteImage={deleteImage} updateImage={(new_img)=> {setImgs(new_img);console.log("in updateImage", new_img.map((it) => it.fileName))}} swipeImage={(indx) => swipeImage(indx)}/> //<<==here is the child DisplayImages. Imgs is a useState() which is an array of images. Its order will be changed if user drag and reorder the array.
                        </MyAccordion>
  )
  

Вот консоль. вывод, показывающий, что порядок изображений Imgs был изменен:

 [Mon Sep 21 2020 23:17:49.624]  LOG       pics before move :  ["26689.1 (5-18-19) image 3.jpg", "4.jpg", "1.jpg"]
[Mon Sep 21 2020 23:17:49.626]  LOG      pics after move :  ["1.jpg", "26689.1 (5-18-19) image 3.jpg", "4.jpg"]
[Mon Sep 21 2020 23:17:49.626]  LOG      in updateImage ["1.jpg", "26689.1 (5-18-19) image 3.jpg", "4.jpg"] //<<==updateImage in DisplayImages calls setImgs to update the Imgs
  

Ожидается, что при обновлении состояния Imgs компонент DisplayImages будет повторно отображаться. Однако это не так. Почему обновление состояния Imgs не вызвало повторной визуализации?

ОБНОВЛЕНИЕ: DisplayImages компонент. DisplayImages заключается в отображении изображений в квадратной сетке, ширина которой может варьироваться в зависимости от общего количества изображений. Обработка жестов используется для обработки перетаскивания и сортировки изображений. Код находится в dev и может быть трудным для чтения.

 import React, { useRef, useMemo, useState, useContext} from 'react';
import {View, FlatList, Image, StyleSheet, ScrollView, TextInput, Platform, TouchableOpacity, SafeAreaView, Dimensions, TouchableWithoutFeedback } from 'react-native';
import { Col, Row, Grid } from 'react-native-easy-grid';
import { PanGestureHandler, PinchGestureHandler, State, RotationGestureHandler } from "react-native-gesture-handler";
import FastImage from "react-native-fast-image";
import Animated, { useValue } from "react-native-reanimated";
const {Value,event,cond,block,set,eq,add,abs,divide,multiply,not,clockRunning,and,or,greaterOrEq,startClock,stopClock,greaterThan,lessThan,call,Clock} = Animated;
import { propsContext} from "../app/GlobalContext";
//import {DeleteButton, GridImage, ModalImage, ModalImageAndroidWrap} from "./viewComponent";

const {width, height} = Dimensions.get("window");

//display images in Col based on number of images
export default DisplayImages = ({pics, deleteImage, updateImage, swipeImage}) => {
    //params 
    const propsVal = useContext(propsContext);
    const device_id=propsVal.device_id, screen_width=width, screen_ht=height;
    //const [tf, setTf] = useState(false);

    if (!pics || pics===[] || pics==={}) return null;
    var len = pics.length, full=0.98, half=0.49, oneThird=0.32; 
    
    console.log("# of images : ", len);
    //check if there is field domain in pics passed in
    const picPath = (item) => {
        if (item.domain) {
            return (item.domain   item.path);
        } else {
            return item.path;
        };
    };

    const move = (from, to) => {
        //
        console.log(" pics before move : ", pics.map((it) => it.fileName));
        var target = pics[from];                         
        var increment = to < from ? -1 : 0;
        pics.splice(from, 1);
        if (Math.abs(to-from)==1) {
            pics.splice(to,0, target);
        } else {
            pics.splice(to increment<=0 ? 0:to increment,0, target);
        };
        console.log("pics after move : ", pics.map((it) => it.fileName));
        updateImage(pics);
      }

    
    function DisplayImg ({img_source, width, ht, index, handleSwipe, modalWidth, modalHt, dataLen, sortFn=null}) {
        const aniIndex= new Value(index);
        const dragX = (useValue(0));
        const dragY = (useValue(0));
        const offsetX = new Value(0);
        const offsetY = new Value(0);
        var transX = useValue(0);
        var transY = useValue(0);
        const state = useValue(-1);
        const scale = useValue(1);
        const rotate = useValue(0);
        const offsetZ = useValue(1);
        var gridPerRow;
        console.log("image width : ", width);
        console.log("image ht : ", ht);
        if (dataLen <= 4 amp;amp; dataLen > 1) {
            gridPerRow = 2;
        } else if (dataLen > 4) {
            gridPerRow = 3;
        } else {
            gridPerRow = 1;
        };
        const aniGridPR = new Value(gridPerRow);
        
                
        function onDrop ([x, y, indx, gridPR]) {
            console.log("x onDrop ", x);
            console.log("y onDrop ", y);
            console.log("index : ", index);
            console.log("Grid PR : ", gridPR);
            var jump_y=0, jump_x=0, new_index, percentage;
            //jump_row               
            jump_y = y>0 ? Math.floor(y/ht) : -Math.floor(-y/ht);
            percentage = Math.abs((y%ht)/ht);
            if (percentage >= 0.3) (y>0 ? jump_y   : jump_y--); //count if Y overlap 30% or more
            console.log("Y jump : ", jump_y);
            //jump col
            jump_x = x>0 ? Math.floor(x/width) : -Math.floor(-x/width);
            //console.log("jump X : ", jump_x);
            percentage = Math.abs((x%width)/width);
            //console.log("col percentage : ", percentage);
            if (percentage >= 0.3) (x>0 ? jump_x   : jump_x--); //count if X overlap 30% or more
            console.log("X jump : ", jump_x);
            new_index = indx   jump_y*gridPR   jump_x
            console.log("new index : ", new_index);
            if (new_index != indx) {
                sortFn(indx, new_index);
            };
        };
        if (img_source amp;amp; img_source!==[] amp;amp; img_source!=={}) {
            console.log("ani code");
            
            const addX = add(offsetX, dragX);
            const addY = add(offsetY, dragY);
            transX = cond(eq(state, State.ACTIVE), addX);
            transY = cond(eq(state, State.ACTIVE), addY, 
                                    cond(eq(state, State.END), 
                                        cond(or(greaterOrEq(abs(divide(dragX,new Value(width))), new Value(0.3)), greaterOrEq(abs(divide(dragY,new Value(ht))), new Value(0.3))), 
                                            call([addX, addY, aniIndex, aniGridPR], onDrop))
                                        )
                                    );
            //console.log("2", transY);
            const handleGesture = event([
                {
                    nativeEvent: {
                    translationX: dragX,
                    translationY: dragY,
                    state,
                    },
                }, 
                ]);
            
            let aniStyle = {
                transform:[
                    { translateX : transX },
                    { translateY : transY },
                    
                ]
            };
            let scaleStyle = {
                transform:[
                    { perspective: 500 },
                    {
                        scale :  scale
                    }
                ]
            };
            return (
                <>
                    
                    <PanGestureHandler 
                        onGestureEvent={handleGesture} 
                        onHandlerStateChange={handleGesture}
                        minPointers={1}
                        maxPointers={1}>
                        <Animated.View style={[aniStyle ]}>
                        <TouchableOpacity onLongPress={() => deleteImage(index)} onPress={()=>handleSwipe()} >
                            <Animated.View style={[styles.wrapper]}>
                                <FastImage 
                                    source={{uri:img_source}} 
                                    resizeMode={FastImage.resizeMode.cover} 
                                    style={[{
                                        width:width, 
                                        height:ht, 
                                        verticalAlign:0,
                                        paddingTop:0
                                    }]}
                                />
                            </Animated.View>
                        </TouchableOpacity> 
                        </Animated.View>
                    </PanGestureHandler>
        
                
                </>
                );
        
            
        } else {
            return null;
        };
        
    };    
    
    //if (len > 0) setImgAccordOpen(true);
    switch(len) {
        case 0:
            return null;
        case 1:
            
            return (
                <Grid style={{position:"absolute", paddingTop:0,paddingLeft:0}}>
                    <Row style={styles.row} key={pics[0].fileName}>
                    <DisplayImg 
                        img_source={picPath(pics[0])}
                        width={screen_width*full}
                        ht={screen_width*full}
                        index= {0}
                        modalWidth={screen_width}
                        modalHt= {pics[0].height*(screen_width/pics[0].width)}
                        dataLen={len}
                        sortFn={move}
                        handleSwipe={swipeImage} 
                    />                   
                    </Row>
                </Grid>
                );
        case 2:
        case 3: 
        case 4:
            
            return (
                <Grid style={{position:"absolute", paddingTop:0,paddingLeft:0}}>
                    {pics.map((item, index) => {
                        if (index%2===0) {  
                            if (pics[index 1]) {
                                return (
                                    <Row style={styles.row} key={pics[index].fileName pics[index 1].fileName}>
                                        
                                        <DisplayImg 
                                            img_source={picPath(pics[index])}
                                            width={screen_width*half}
                                            ht={screen_width*half}
                                            index= {index}
                                            modalWidth={screen_width}
                                            modalHt= {pics[index].height*(screen_width/pics[index].width)}
                                            dataLen={len}
                                            sortFn={move}
                                            handleSwipe={swipeImage} 
                                        />
                                       
                                        <DisplayImg 
                                            img_source={picPath(pics[index 1])}
                                            width={screen_width*half}
                                            ht={screen_width*half}
                                            index= {index 1}
                                            modalWidth={screen_width}
                                            modalHt= {pics[index 1].height*(screen_width/pics[index 1].width)}
                                            dataLen={len}
                                            sortFn={move}
                                            handleSwipe={swipeImage} 
                                        />                                                                    
                                    </Row>
                                )} else {
                                return (
                                    <Row style={styles.row} key={pics[index].fileName}>
                                        <DisplayImg 
                                            img_source={picPath(pics[index])}
                                            width={screen_width*half}
                                            ht={screen_width*half}
                                            index= {index}
                                            modalWidth={screen_width}
                                            modalHt= {pics[index].height*(screen_width/pics[index].width)}
                                            dataLen={len}
                                            sortFn={move}
                                            handleSwipe={swipeImage} 
                                        />
                                        
                                    </Row>    
                                )};                                           
                        }
                    })}                        
                </Grid>
            );

        default:
            return (
                <Grid style={{position:"absolute", paddingTop:0,paddingLeft:0}}>
                    {pics.map((item, index) => {
                        if (index%3===0) {  
                            if (pics[index 2]) {
                                return (
                                    <Row style={styles.row} key={pics[index].fileName pics[index 1].fileName pics[index 2].fileName}>
                                        <DisplayImg 
                                            img_source={picPath(pics[index])}
                                            width={screen_width*oneThird}
                                            ht={screen_width*oneThird}
                                            index= {index}
                                            modalWidth={screen_width}
                                            modalHt= {pics[index].height*(screen_width/pics[index].width)}
                                            dataLen={len}
                                            sortFn={move}
                                            handleSwipe={swipeImage}
                                            
                                        />
                                        <DisplayImg 
                                            img_source={picPath(pics[index 1])}
                                            width={screen_width*oneThird}
                                            ht={screen_width*oneThird}
                                            index= {index 1}
                                            modalWidth={screen_width}
                                            modalHt= {pics[index 1].height*(screen_width/pics[index 1].width)}
                                            dataLen={len}
                                            sortFn={move}
                                            handleSwipe={swipeImage}
                                           
                                        />
                                        <DisplayImg 
                                            img_source={picPath(pics[index 2])}
                                            width={screen_width*oneThird}
                                            ht={screen_width*oneThird}
                                            index= {index 2}
                                            modalWidth={screen_width}
                                            modalHt= {pics[index 2].height*(screen_width/pics[index 2].width)}
                                            dataLen={len}
                                            sortFn={move}
                                            handleSwipe={swipeImage}
                                            
                                        />
                                        
                                    </Row>    
                            )} else if (pics[index 1]) {
                                return (
                                    <Row style={styles.row} key={pics[index].fileName pics[index 1].fileName}>
                                        <DisplayImg 
                                            img_source={picPath(pics[index])}
                                            width={screen_width*oneThird}
                                            ht={screen_width*oneThird}
                                            index= {index}
                                            modalWidth={screen_width}
                                            modalHt= {pics[index].height*(screen_width/pics[index].width)}
                                            dataLen={len}
                                            sortFn={move}
                                            handleSwipe={swipeImage}
                                            
                                        />
                                        <DisplayImg 
                                            img_source={picPath(pics[index 1])}
                                            width={screen_width*oneThird}
                                            ht={screen_width*oneThird}
                                            index= {index 1}
                                            modalWidth={screen_width}
                                            modalHt= {pics[index 1].height*(screen_width/pics[index 1].width)}
                                            dataLen={len}
                                            sortFn={move}
                                            handleSwipe={swipeImage}
                                            
                                        />
                                    </Row>    
                            )} else if (!pics[index 1]) {
                                return (
                                    <Row style={styles.row} key={pics[index].fileName}>
                                        <DisplayImg 
                                            img_source={picPath(pics[index])}
                                            width={screen_width*oneThird}
                                            ht={screen_width*oneThird}
                                            index= {index}
                                            modalWidth={screen_width}
                                            modalHt= {pics[index].height*(screen_width/pics[index].width)}
                                            dataLen={len}
                                            sortFn={move}
                                            handleSwipe={swipeImage}
                                           
                                        />
                                    </Row>
                            )} else {
                                return null;
                            };                                           
                        }
                    })}                        
                </Grid>
            );

        };        
        
};
  

Комментарии:

1. Пожалуйста, отправьте код из DisplayImages компонента.

2. SaachiTech , разместил компонент. Код находится в dev и может быть беспорядочным. Спасибо.

3. Это потому, что 2 массива с одинаковыми элементами, но в разном порядке, одинаковы в javascript?

4. После добавления состояния flip flip={flip} и setFlip(!flip) updateImages повторная визуализация начинает работать.

Ответ №1:

Функция React.memo, которая может использоваться для определения, должен ли компонент отображаться или нет в перехватах, если функция возвращает true, тогда компонент не будет повторно отображаться при изменении этого реквизита, наоборот, он обновляется, когда возвращаемое значение равно false

 function Itie({prop1, prop2}) {
    return(
        ..
    )

}
React.memo(Itie, (props, nextProps)=> {
    if(props.prop1 === nextProps.prop1) {
        // don't re-render/update
        return true
    }
    
})
  

Ответ №2:

Проблема отсутствия рендеринга может быть вызвана 2 массивами с точно такими же элементами, но в Javascript обрабатывается разный порядок. Для принудительной повторной визуализации flip к компоненту добавляется логическое состояние Itie . Всякий раз, когда происходит изменение порядка с массивом изображений, flip изменяется его значение, которое запускает повторную визуализацию. Вот соответствующий код:

 export default function Itie({navigation}) {
    const [flip, setFlip] = useState(false);
    //do something
    return (
         <MyAccordion 
              title={"Image"} 
              absPosition={false} 
              screenSize={{width:screen_width, height:((imgs amp;amp; imgs.length>9) ? screen_width (screen_width/3)*(Math.ceil(((imgs.length-9)%3)/3)):screen_width)}} 
              ref= {refImg}
                        >
               <DisplayImages pics={imgs} deleteImage={deleteImage} flip={flip} updateImage={(new_img) => {setImgs(new_img);setFlip(!flip);}} swipeImage={swipeImage}/> 
          </MyAccordion>
)
  

Комментарии:

1. Если массив не содержит больших данных, вы можете создать новый массив, например [… lastarray] , это будет новый экземпляр массива и также вызовет повторную визуализацию.