Проблема:
При использовании ScrollView в ReactNative для горизонтальной разбивки на страницы он повторно отображает все дочерние элементы, но я хотел бы сохранить значения состояния определенных локальных полей ввода и локальных переменных дочерних компонентов.
В приведенном ниже коде, если я находился в середине обновления TextInput в NotesSection, но хотел вернуться к BatchSection, чтобы просмотреть некоторые метаданные, код повторно отображает NotesSection и сбрасывает локальное состояние, содержащее текстовое значение.
Диагноз:
Я очень новичок в React и React Native, но я думаю, что это происходит из-за родительской переменной состояния «horizontalPos», которая принимает целое число, чтобы отразить, какая страница находится в фокусе. Это просто используется в компоненте ProductHeader для выделения цветной нижней границы, показывающей пользователю своего рода небольшое «меню» в верхней части экрана. Состояние «horizontalPos» можно обновить двумя способами:
Вопрос/Решение:
Если бы состояние «horizontalPos» было ВНУТРИ ProductHeader, я подозреваю, что это решило бы проблему, но я не могу понять, как это сделать, поскольку я не верю, что возможно передать функцию дочернему элементу на основе изменения в родительский компонент.
Я зависим от регистрации OnScroll в основном ScrollView, а остальные компоненты также должны находиться внутри основного ScrollView, но я не хочу, чтобы они повторно отображались каждый раз, когда обновляется состояние «horizontalPos».
Код:
const ProductScreen = (props) => {
const [horizontalPos, setHorizontalPos] = useState(0)
const scrollRef = useRef()
const toggleHorizontal = (page) => {
setHorizontalPos(page)
scrollRef.current.scrollTo({x:page*width, y:0, animated:false})
}
const handleHorizontalScroll = (v) => {
const pagination = Math.round(v.nativeEvent.contentOffset.x / width)
if (pagination != horizontalPos){
setHorizontalPos(pagination)
}
}
const ProductHeader = () => {
return(
<View style = {styles.scrollHeaderContainer}>
<TouchableOpacity style = {[styles.scrollHeader, horizontalPos == 0 ? {borderColor: AppGreenDark,} : null]} onPress = {() => toggleHorizontal(0)}>
<Text style = {styles.scrollHeaderText}>Meta Data</Text>
</TouchableOpacity>
<TouchableOpacity style = {[styles.scrollHeader, horizontalPos == 1 ? {borderColor: AppGreenDark,} : null]} onPress = {() => toggleHorizontal(1)}>
<Text style = {styles.scrollHeaderText}>{"Notes"}</Text>
</TouchableOpacity>
</View>
)
}
return (
<View style = {styles.container}>
<ProductHeader/>
<ScrollView
ref = {scrollRef}
decelerationRate = {'fast'}
horizontal = {true}
showsHorizontalScrollIndicator = {false}
snapToInterval = {width}
onScroll = {handleHorizontalScroll}
scrollEventThrottle = {16}
disableIntervalMomentum = {true}
style = {{flex: 1}}
>
<View style = {[styles.horizontalScroll]}>
<View style = {styles.mainScrollView}>
<BatchSection/>
</View>
<ScrollView style = {styles.notesScrollView}>
<NotesSection/>
</ScrollView>
</View>
</ScrollView>
</View>
)
}



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Как вы отметили, обновление состояния horizontalPos внутри ProductScreen вызовет повторную визуализацию всего экрана, что не является ожидаемым поведением.
Чтобы избежать этого сценария, давайте рефакторим код, как показано ниже:
function debounce(func, timeout = 500){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
class ProductHeader extends React.Component {
state = {horizontalPos:0 }
toggleHorizontal = (page) => {
this.setState({horizontalPos:page});
this.props.onPositionChange(page);
};
render () {
const {horizontalPos} = this.state
return (
<View style = {styles.scrollHeaderContainer}>
<TouchableOpacity
style = {[
styles.scrollHeader,
horizontalPos == 0 ? { borderColor: AppGreenDark } : null,
]}
onPress = {() => this.toggleHorizontal(0)}
>
<Text style = {styles.scrollHeaderText}>Meta Data</Text>
</TouchableOpacity>
<TouchableOpacity
style = {[
styles.scrollHeader,
horizontalPos == 1 ? { borderColor: AppGreenDark } : null,
]}
onPress = {() => this.toggleHorizontal(1)}
>
<Text style = {styles.scrollHeaderText}>{"Notes"}</Text>
</TouchableOpacity>
</View>
);
}
};
const ProductScreen = (props) => {
const scrollRef = useRef();
const productHeaderRef = useRef()
let horizontalPos = 0;
const handleHorizontalScroll = (v) => {
const pagination = Math.round(v.nativeEvent.contentOffset.x / width);
if (pagination != horizontalPos) {
productHeaderRef.current?.toggleHorizontal(pagination)
}
};
const debouncedHorizontalScroll= debounce(handleHorizontalScroll,500)
const onPositionChange = (page) => {
horizontalPos = page;
scrollRef.current.scrollTo({ x: page * width, y: 0, animated: false });
};
return (
<View style = {styles.container}>
<ProductHeader onPositionChange = {onPositionChange} ref = {productHeaderRef} />
<ScrollView
ref = {scrollRef}
decelerationRate = {"fast"}
horizontal = {true}
showsHorizontalScrollIndicator = {false}
snapToInterval = {width}
onScroll = {debouncedHorizontalScroll}
scrollEventThrottle = {16}
disableIntervalMomentum = {true}
style = {{ flex: 1 }}
>
<View style = {[styles.horizontalScroll]}>
<View style = {styles.mainScrollView}>
<BatchSection />
</View>
<ScrollView style = {styles.notesScrollView}>
<NotesSection />
</ScrollView>
</View>
</ScrollView>
</View>
);
};
Я надеюсь, что это остановит перерисовку всего экрана и сохранение нумерации страниц.
Можно просмотреть новое обновление решения. Я исправил эту проблему
На один шаг ближе, но он все еще не может найти setHorizontalPos в handleHorizontalScroll. Я также обновил это до horizontalPos = pagination, что останавливает выдачу ошибок и останавливает повторную визуализацию. У меня все еще есть проблема, что состояние ProductHeader не настраивается при прокрутке. В настоящее время мне нужно это, чтобы настроить цвет границы, чтобы отразить, где сфокусировано. Имеет ли это смысл?
Можете ли вы протестировать другой подход в обновленном решении
Куда-нибудь сейчас, спасибо! Не знал, что через useRef можно получить доступ к дочерним методам. Это технически решает исходный вопрос, спасибо. Однако есть небольшая проблема с производительностью. Скролл сейчас очень "дергается". Как только он достигает половины, он переходит к следующему интервалу ширины. Я предполагаю, что это основано на snapToInterval. У вас случайно нет простого решения для этого?
Состояние обновления для каждого события прокрутки является узким местом производительности, и его следует избегать.
Я нашел решение, переместив изменение onPosition и новое состояние, которое вы создали. Большое спасибо за помощь, Фистон. Очень признателен
Рад слышать. Я обновил решение с некоторой оптимизацией производительности с отменой событий прокрутки. Узнайте, должно ли это дать вам некоторое повышение производительности.
Спасибо за предложение. К сожалению, рефакторинг состояния horizontalPos внутри ProductHeader делает его недоступным из-за того, что ScrollView использует handleHorizonScroll через onScroll, что затем выдает ошибку о том, что на «horizontalPos» ссылаются до объявления :(