ScrollView Pagination заставляет все дочерние элементы повторно отображать

Проблема:

При использовании ScrollView в ReactNative для горизонтальной разбивки на страницы он повторно отображает все дочерние элементы, но я хотел бы сохранить значения состояния определенных локальных полей ввода и локальных переменных дочерних компонентов.

В приведенном ниже коде, если я находился в середине обновления TextInput в NotesSection, но хотел вернуться к BatchSection, чтобы просмотреть некоторые метаданные, код повторно отображает NotesSection и сбрасывает локальное состояние, содержащее текстовое значение.

Диагноз:

Я очень новичок в React и React Native, но я думаю, что это происходит из-за родительской переменной состояния «horizontalPos», которая принимает целое число, чтобы отразить, какая страница находится в фокусе. Это просто используется в компоненте ProductHeader для выделения цветной нижней границы, показывающей пользователю своего рода небольшое «меню» в верхней части экрана. Состояние «horizontalPos» можно обновить двумя способами:

  1. Первый — просто щелкнуть нужный заголовок (TouchableOpacity) в ProductHeader, который вызывает изменение состояния и использует useRef для автоматического перемещения ScrollView.
  2. Второй вариант — когда пользователь проводит пальцем по ScrollView. Использование OnScroll для запуска функции «handleHorizontalScroll», которая, в свою очередь, устанавливает состояние «horizontalPos», используя простую математику из contentOffset.x.

Вопрос/Решение:

Если бы состояние «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>
    )
}
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
0
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как вы отметили, обновление состояния 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>
  );
};



Я надеюсь, что это остановит перерисовку всего экрана и сохранение нумерации страниц.

Спасибо за предложение. К сожалению, рефакторинг состояния horizontalPos внутри ProductHeader делает его недоступным из-за того, что ScrollView использует handleHorizonScroll через onScroll, что затем выдает ошибку о том, что на «horizontalPos» ссылаются до объявления :(

BookofMorten 27.12.2022 12:37

Можно просмотреть новое обновление решения. Я исправил эту проблему

Fiston Emmanuel 27.12.2022 12:41

На один шаг ближе, но он все еще не может найти setHorizontalPos в handleHorizontalScroll. Я также обновил это до horizontalPos = pagination, что останавливает выдачу ошибок и останавливает повторную визуализацию. У меня все еще есть проблема, что состояние ProductHeader не настраивается при прокрутке. В настоящее время мне нужно это, чтобы настроить цвет границы, чтобы отразить, где сфокусировано. Имеет ли это смысл?

BookofMorten 27.12.2022 13:01

Можете ли вы протестировать другой подход в обновленном решении

Fiston Emmanuel 27.12.2022 14:13

Куда-нибудь сейчас, спасибо! Не знал, что через useRef можно получить доступ к дочерним методам. Это технически решает исходный вопрос, спасибо. Однако есть небольшая проблема с производительностью. Скролл сейчас очень "дергается". Как только он достигает половины, он переходит к следующему интервалу ширины. Я предполагаю, что это основано на snapToInterval. У вас случайно нет простого решения для этого?

BookofMorten 27.12.2022 14:51

Состояние обновления для каждого события прокрутки является узким местом производительности, и его следует избегать.

Fiston Emmanuel 27.12.2022 15:02

Я нашел решение, переместив изменение onPosition и новое состояние, которое вы создали. Большое спасибо за помощь, Фистон. Очень признателен

BookofMorten 27.12.2022 15:05

Рад слышать. Я обновил решение с некоторой оптимизацией производительности с отменой событий прокрутки. Узнайте, должно ли это дать вам некоторое повышение производительности.

Fiston Emmanuel 27.12.2022 15:12

Другие вопросы по теме