Как стать автором
Обновить

React Native scroll to element

Автор оригинала: Viktor Chukhlov

Прежде чем вы погрузитесь в чтение моего решения, я хочу пригласить вас в свой Telegram-канал. Там можно найти хорошие предложения удаленной работы. Этот канал создан только для разработчиков.

Начинаем

Работая с React Native, иногда нам нужно найти способ прокрутки до определенного элемента. Это просто, когда scrollview и все необходимые элементы лежат в одном дереве DOM.

Представьте себе случай, у вас в приложении есть Drawer Navigation, по нажатию на определенную кнопку из бокового меню, приложение должно закрыть sidebar, перейти на нужный экран и перейти к определенному элементу. Мы понимаем, что кнопка из панели навигации, scrollview и необходимый элемент основаны на разных деревьях DOM.

Моё решение

class Refs {
  private _elements = new Map();

  public setRef = (key: string) => (ref: any) => {
    this._elements.set(key, ref);
  };

  public getRef(key: string) {
    return this._elements.get(key);
  }

  public remove(key: string) {
    this._elements.delete(key);
  }

  public clear() {
    this._elements.clear();
  }
}

export default new Refs();

Теперь у нас есть класс, в котором мы можем хранить ссылки на необходимые элементы и манипулировать ими. Если у вас есть вопросы по поводу моих решений, задавайте их в Telegram Channel!

Создадим Drawer Navigation и воспользуемся методом setRef

import React, {FC} from 'react';
import {View, SafeAreaView} from 'react-native';
import Refs from '../../../helpers/refs';
import {
  DrawerContentScrollView,
  DrawerContentComponentProps,
} from '@react-navigation/drawer';
import DrawerHeader from './drawerHeader';
import DrawerMenu from './drawerMenu';
import DrawerButtons from './drawerButtons';
import styles from './styles';

const DrawerContent: FC<DrawerContentComponentProps> = ({navigation}) => {
  Refs.setRef('navigation')(navigation);
  // Запишем ссылку на навигацию в обьект нашего класса

  return (
    <View style={styles.menu}>
      <DrawerContentScrollView>
        <DrawerHeader />
        <DrawerMenu />
      </DrawerContentScrollView>

      <SafeAreaView>
        <DrawerButtons />
      </SafeAreaView>
    </View>
  );
};

export default DrawerContent;

Создадим ScrollView и добавим ссылку на его Ref с помощью setRef

import React, {FC, useEffect} from 'react';
import {View, SafeAreaView, ScrollView} from 'react-native';
import {DrawerContentComponentProps} from '@react-navigation/drawer';
import Refs from '../../helpers/refs';
import Statistics from '../../components/statistics';
import Header from '../../components/header';
import Goals from '../../components/goals';
import Offers from '../../components/offers';
import styles from './styles';

const Main: FC<DrawerContentComponentProps> = ({navigation}) => {
  useEffect(() => {
    return () => Refs.remove('scroll'); // remove scroll on unmount 
  }, []);

  return (
    <SafeAreaView style={styles.body}>
      <ScrollView ref={Refs.setRef('scroll')}> // add scroll to Refs
        <View style={styles.statisticsBar} />
        <Statistics />
        <Header {...navigation} />
        <Goals />
        <Offers />
      </ScrollView>
    </SafeAreaView>
  );
};

export default Main;

Создадим компонент Offers и добавим ссылку на его Ref с помощью setRef

import React, {useEffect, useMemo, useContext} from 'react';
import {View} from 'react-native';
import Refs from '../../helpers/refs';
import {TabsContext} from '../../providers/tabs';
import Tabs from '../tabs';
import OffersList from './list';
import styles from './styles';

const OffersView = () => {
  const [tab] = useContext(TabsContext);

  useEffect(() => {
    return () => Refs.remove('offers'); // remove reference link 
  }, []);

  return (
    <>
      <Tabs />
      <View style={styles.offers} ref={Refs.setRef('offers')}> // add offers view element to Refs
        <OffersList />
      </View>
    </>
  );
};

export default OffersView;

Пишем метод scrollToOffers

import Refs from '../../../helpers/refs';

const scrollToOffers = () => {
  const scroll = Refs.getRef('scroll');
  const offers = Refs.getRef('offers');
  const navigation = Refs.getRef('navigation');

  offers?.measure((x: number, y: number) => {
    navigation?.jumpTo('Main');
    scroll?.scrollTo({x, y, animated: true});
  });
};

Обязательно почистим за собой Refs class

import React, {useEffect} from 'react';
import Refs from './helpers/refs';
import DrawerNavigation from './stack/drawer';

function App() {
  useEffect(() => {
    return () => Refs.clear(); // clear whole map from Refs class
  }, []);

  return (
     <DrawerNavigation />
  );
}

export default App;

Подведем итоги

Почему я не использовал HOC или хуки, чтобы закрыть эту задачу?

Потому что я не хочу повторно рендерить какие-либо компоненты. Основная цель состояла в том, чтобы найти способ прокрутки в условии, когда элементы находятся на разных уровнях. В этом случае мне не нужны обновления DOM, это означает, что мы можем использовать ссылки для существующих элементов и манипулировать ими.

Подпишитесь на мой Telegram канал, здесь вы найдете предложения удаленной работы с высокими рейтами.

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.