#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] , это будет новый экземпляр массива и также вызовет повторную визуализацию.