Перейти к содержанию

React Native Tab View

React Native Tab View - это кроссплатформенный компонент Tab View для React Native, реализованный с использованием react-native-pager-view на Android и iOS и PanResponder на Web, macOS и Windows.

По умолчанию он соответствует принципам материального дизайна, но вы можете использовать собственную пользовательскую панель вкладок или расположить ее внизу.

React Native Tab View

Данный пакет не интегрируется с React Navigation. Если вы хотите интегрировать представление вкладок с системой навигации React Navigation, например, отображать экраны в панели вкладок и иметь возможность переходить между ними с помощью navigation.navigate и т.д., используйте вместо этого Material Top Tab Navigator.

Установка

Чтобы использовать этот пакет, откройте Терминал в корне проекта и выполните команду:

1
npm install react-native-tab-view

Далее установите react-native-pager-view, если вы планируете поддерживать iOS и Android.

Если вы используете Expo, то для обеспечения совместимости версий библиотек выполните команду:

1
expo install react-native-pager-view

Если вы не используете Expo, выполните следующее:

1
npm install react-native-pager-view

Готово! Теперь вы можете собрать и запустить приложение на своем устройстве/симуляторе.

Быстрый старт

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import * as React from 'react';
import { View, useWindowDimensions } from 'react-native';
import { TabView, SceneMap } from 'react-native-tab-view';

const FirstRoute = () => (
    <View style={{ flex: 1, backgroundColor: '#ff4081' }} />
);

const SecondRoute = () => (
    <View style={{ flex: 1, backgroundColor: '#673ab7' }} />
);

const renderScene = SceneMap({
    first: FirstRoute,
    second: SecondRoute,
});

export default function TabViewExample() {
    const layout = useWindowDimensions();

    const [index, setIndex] = React.useState(0);
    const [routes] = React.useState([
        { key: 'first', title: 'First' },
        { key: 'second', title: 'Second' },
    ]);

    return (
        <TabView
            navigationState={{ index, routes }}
            renderScene={renderScene}
            onIndexChange={setIndex}
            initialLayout={{ width: layout.width }}
        />
    );
}

Попробуйте рассмотреть этот пример на Snack

Другие примеры на Snack

Справочник по API

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

TabView

Контейнерный компонент, отвечающий за отрисовку и управление вкладками. По умолчанию использует стили Material Design.

Базовое использование выглядит следующим образом:

1
2
3
4
5
6
7
8
<TabView
    navigationState={{ index, routes }}
    onIndexChange={setIndex}
    renderScene={SceneMap({
        first: FirstRoute,
        second: SecondRoute,
    })}
/>

Параметры TabView:

navigationState (required)

Состояние для представления вкладки. Состояние должно содержать следующие свойства:

  • index: число, представляющее индекс активного маршрута в массиве routes.
  • routes: массив, содержащий список объектов маршрутов, используемых для отображения вкладок.

Каждый объект маршрута должен содержать следующие свойства:

  • key: уникальный ключ для идентификации маршрута (обязательно)
  • title: заголовок маршрута для отображения на панели вкладок
  • icon: иконка маршрута для отображения на панели вкладок
  • accessibilityLabel: метка доступности для кнопки вкладки
  • testID: идентификатор теста для кнопки вкладки

Пример:

1
2
3
4
5
6
7
8
9
{
  index: 1,
  routes: [
    { key: 'music', title: 'Music' },
    { key: 'albums', title: 'Albums' },
    { key: 'recents', title: 'Recents' },
    { key: 'purchased', title: 'Purchased' },
  ]
}

TabView является управляемым компонентом, а значит, index должен обновляться через обратный вызов onIndexChange.

onIndexChange (required)

Обратный вызов, который вызывается при смене вкладки и получает в качестве аргумента индекс новой вкладки.

При его вызове состояние навигации должно быть обновлено, иначе изменение будет отменено.

renderScene (required)

Обратный вызов, который возвращает элемент react для рендеринга в качестве страницы вкладки. В качестве аргумента получает объект, содержащий маршрут:

1
2
3
4
5
6
7
8
const renderScene = ({ route, jumpTo }) => {
    switch (route.key) {
        case 'music':
            return <MusicRoute jumpTo={jumpTo} />;
        case 'albums':
            return <AlbumsRoute jumpTo={jumpTo} />;
    }
};

Для повышения производительности необходимо убедиться в том, что отдельные маршруты реализуют shouldComponentUpdate. Для упрощения задания компонентов можно использовать помощник SceneMap.

SceneMap принимает объект с отображением route.key на компоненты React и возвращает функцию для использования в свойстве renderScene.

1
2
3
4
5
6
7
8
import { SceneMap } from 'react-native-tab-view';

...

const renderScene = SceneMap({
  music: MusicRoute,
  albums: AlbumsRoute,
});

Такое задание компонентов проще и позволяет обойтись без реализации метода shouldComponentUpdate.

Каждая сцена получает следующие реквизиты:

  • route: текущий маршрут, отображаемый компонентом
  • jumpTo: метод для перехода на другие вкладки, в качестве аргумента принимает route.key.
  • position: анимированный узел, представляющий текущую позицию

Метод jumpTo может быть использован для программного перехода на другие вкладки:

1
props.jumpTo('albums');

Все сцены, отрисованные с помощью SceneMap, оптимизированы с использованием React.PureComponent и не перерисовываются при изменении реквизитов или состояний родителя. Если вам нужен больший контроль над обновлением сцен (например, инициирование повторного рендеринга, даже если navigationState не изменилось), используйте renderScene напрямую, а не с помощью SceneMap.

ВАЖНО: Не передавайте в SceneMap инлайн-функции, например, не делайте следующее:

1
2
3
4
SceneMap({
    first: () => <FirstRoute foo={props.foo} />,
    second: SecondRoute,
});

Всегда определяйте свои компоненты в другом месте на верхнем уровне файла. Если вы передаете inline-функции, то при каждом рендеринге компонент будет создаваться заново, что приведет к размонтированию и повторному монтированию всего маршрута при каждом изменении. Это очень плохо сказывается на производительности, а также приводит к потере всех локальных состояний.

Если необходимо передать дополнительные реквизиты, используйте пользовательскую функцию renderScene:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const renderScene = ({ route }) => {
    switch (route.key) {
        case 'first':
            return <FirstRoute foo={this.props.foo} />;
        case 'second':
            return <SecondRoute />;
        default:
            return null;
    }
};

renderTabBar

Обратный вызов, который возвращает пользовательский элемент React Element для использования в качестве панели вкладок:

1
2
3
4
5
6
7
8
import { TabBar } from 'react-native-tab-view';

// ...

<TabView
    renderTabBar={(props) => <TabBar {...props} />}
    // ...
/>;

Если этот реквизит не указан, то отображается панель вкладок по умолчанию. Передавая этот реквизит, можно настроить панель вкладок по умолчанию, создать свою собственную панель вкладок или полностью отключить панель вкладок.

1
2
3
4
<TabView
    renderTabBar={() => null}
    // ...
/>

tabBarPosition

Положение панели вкладок в представлении вкладок. Возможные значения: top и bottom. По умолчанию принимается значение top.

lazy

Функция принимает объект с текущим маршрутом и возвращает булево значение, указывающее, нужно ли лениво отрисовывать сцены.

По умолчанию все сцены отображаются, чтобы обеспечить более плавное пролистывание. Однако вы можете захотеть отложить отрисовку несфокусированных сцен до тех пор, пока пользователь не увидит их. Чтобы включить ленивый рендеринг для конкретной сцены, верните true из getLazy для этого route:

1
2
3
4
<TabView
    lazy={({ route }) => route.name === 'Albums'}
    // ...
/>

Когда вы включаете ленивый рендеринг для экрана, обычно требуется некоторое время для рендеринга, когда он оказывается в фокусе. Вы можете использовать параметр renderLazyPlaceholder для настройки того, что пользователь видит в течение этого короткого периода.

Можно также передать булево значение, чтобы включить ленивый рендеринг для всех сцен:

1
<TabView lazy />

lazyPreloadDistance

Когда lazy включен, можно указать, сколько соседних маршрутов должно быть предварительно загружено с помощью данного параметра. По умолчанию это значение равно 0, что означает, что ленивые страницы загружаются по мере их попадания в область просмотра.

renderLazyPlaceholder

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

Это представление обычно показывается только на долю секунды. Необходимо поддерживать его легковесным.

По умолчанию отображается null.

keyboardDismissMode

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

  • 'auto' (по умолчанию): клавиатура отключается при изменении индекса.
  • 'on-drag': клавиатура отключается, когда начинается перетаскивание.
  • 'none': перетаскивание не приводит к отключению клавиатуры.

swipeEnabled

Булево значение, указывающее, следует ли включить жесты пролистывания. По умолчанию жесты пролистывания включены. Если передать false, то жесты пролистывания будут отключены, но пользователь по-прежнему сможет переключать вкладки, нажимая на панель вкладок.

animationEnabled

Включает анимацию при смене вкладки. По умолчанию это значение равно true.

onSwipeStart

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

onSwipeEnd

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

initialLayout

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

1
2
3
4
5
6
<TabView
    initialLayout={{
        width: Dimensions.get('window').width,
    }}
    // ...
/>

overScrollMode

Используется для переопределения значения по умолчанию режима прокрутки пейджера. Может иметь значение auto, always или never (только для Android).

sceneContainerStyle

Стиль, применяемый к представлению, оборачивающему каждый экран. Его можно передать, чтобы отменить некоторые стили по умолчанию, например, обрезание переполнения:

pagerStyle

Стиль, который будет применяться к представлению пейджера, оборачивающему все сцены.

style

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

TabBar

Тематическая панель вкладок в стиле Material Design. Для настройки панели вкладок необходимо использовать свойство renderTabBar функции TabView для рендеринга TabBar и передать дополнительные реквизиты.

Например, чтобы настроить цвет индикатора и цвет фона панели вкладок, можно передать реквизиты indicatorStyle и style в TabBar соответственно:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const renderTabBar = (props) => (
    <TabBar
        {...props}
        indicatorStyle={{ backgroundColor: 'white' }}
        style={{ backgroundColor: 'pink' }}
    />
);

//...

return (
    <TabView
        renderTabBar={renderTabBar}
        // ...
    />
);

Параметры TabBar:

getLabelText

Функция, принимающая объект с текущим маршрутом и возвращающая текст метки для вкладки. По умолчанию используется route.title.

1
2
3
4
<TabBar
    getLabelText={({ route }) => route.title}
    // ...
/>

getAccessible

Функция принимает объект с текущим маршрутом и возвращает логическое значение, указывающее, следует ли пометить вкладку как accessible. По умолчанию принимает значение true.

getAccessibilityLabel

Функция, принимающая объект с текущим маршрутом и возвращающая метку доступности для кнопки вкладки. По умолчанию используется route.accessibilityLabel, если он указан, в противном случае используется заголовок маршрута.

1
2
3
4
5
6
<TabBar
    getAccessibilityLabel={({ route }) =>
        route.accessibilityLabel
    }
    // ...
/>

getTestID

Функция, принимающая объект с текущим маршрутом и возвращающая тестовый идентификатор кнопки вкладки для определения местоположения этой кнопки в тестах. По умолчанию используется route.testID.

1
2
3
4
<TabBar
    getTestID={({ route }) => route.testID}
    // ...
/>

renderIcon

Функция принимает объект с текущим маршрутом, сфокусированным статусом и цветом и возвращает пользовательский React-элемент для использования в качестве иконки.

1
2
3
4
5
6
7
8
9
<TabBar
    renderIcon={({ route, focused, color }) => (
        <Icon
            name={focused ? 'albums' : 'albums-outlined'}
            color={color}
        />
    )}
    // ...
/>

renderLabel

Функция принимает объект с текущим маршрутом, сфокусированным статусом и цветом и возвращает пользовательский React-элемент для использования в качестве метки.

1
2
3
4
5
6
7
8
<TabBar
    renderLabel={({ route, focused, color }) => (
        <Text style={{ color, margin: 8 }}>
            {route.title}
        </Text>
    )}
    // ...
/>

renderTabBarItem

Функция, принимающая объект TabBarItemProps и возвращающая пользовательский React-элемент, который будет использоваться в качестве кнопки вкладки.

renderIndicator

Функция, которая принимает объект с текущим маршрутом и возвращает пользовательский React-элемент для использования в качестве индикатора вкладки.

renderBadge

Функция, которая принимает объект с текущим маршрутом и возвращает пользовательский React-элемент для использования в качестве бейджа.

onTabPress

Функция, выполняемая при нажатии на вкладку. Она получает сцену для нажатой вкладки, что полезно для таких вещей, как прокрутка к верху.

По умолчанию нажатие вкладки также переключает вкладку. Чтобы предотвратить такое поведение, можно вызвать функцию preventDefault:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<TabBar
    onTabPress={({ route, preventDefault }) => {
        if (route.key === 'home') {
            preventDefault();

            // Do something else
        }
    }}
    // ...
/>

onTabLongPress

Функция, выполняемая при длительном нажатии на вкладку, используется для отображения меню с дополнительными опциями

activeColor

Пользовательский цвет для иконки и ярлыка на активной вкладке.

inactiveColor

Пользовательский цвет для иконки и ярлыка неактивной вкладки.

pressColor

Цвет для пульсации материала (только для Android >= 5.0).

pressOpacity

Непрозрачность для вкладки с нажатой кнопкой (только для iOS и Android < 5.0).

scrollEnabled

Булево значение, указывающее, следует ли сделать панель вкладок прокручиваемой.

При установке scrollEnabled в true следует также указать width в tabStyle для улучшения начального рендеринга.

bounces

Булево значение, указывающее, отскакивает ли панель вкладок при прокрутке.

tabStyle

Стиль, применяемый к отдельным элементам вкладки на панели вкладок.

По умолчанию все элементы вкладки занимают одну и ту же предварительно рассчитанную ширину, основанную на ширине контейнера. Если вы хотите, чтобы они имели свою первоначальную ширину, то можете указать width: 'auto' в tabStyle.

indicatorStyle

Стиль, применяемый к активному индикатору.

indicatorContainerStyle

Стиль, применяемый к представлению контейнера для индикатора.

labelStyle

Стиль, применяемый к метке элемента вкладки.

ContentContainerStyle

Стиль, применяемый к внутреннему контейнеру для вкладок.

style (TabBar)

Стиль, применяемый к контейнеру панели вкладок.

gap

Определяет расстояние между вкладками.

testID

Тестовый идентификатор для панели вкладок. Может использоваться для прокрутки панели вкладок в тестах

Советы по оптимизации

Избегайте ненужных повторных рендеров

Функция renderScene вызывается каждый раз при изменении индекса. Если функция renderScene является дорогостоящей, то для предотвращения ненужных повторных рендеров целесообразно переместить каждый маршрут в отдельный компонент, если он не зависит от индекса, и использовать shouldComponentUpdate или React.memo в компонентах маршрута.

Например, вместо:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const renderScene = ({ route }) => {
    switch (route.key) {
        case 'home':
            return (
                <View style={styles.page}>
                    <Avatar />
                    <NewsFeed />
                </View>
            );
        default:
            return null;
    }
};

Выполните следующие действия:

1
2
3
4
5
6
7
8
const renderScene = ({ route }) => {
    switch (route.key) {
        case 'home':
            return <HomeComponent />;
        default:
            return null;
    }
};

Где <HomeComponent /> - это PureComponent, если вы используете компоненты класса:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
export default class HomeComponent extends React.PureComponent {
    render() {
        return (
            <View style={styles.page}>
                <Avatar />
                <NewsFeed />
            </View>
        );
    }
}

Или обернуть в React.memo, если вы используете функциональные компоненты:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function HomeComponent() {
    return (
        <View style={styles.page}>
            <Avatar />
            <NewsFeed />
        </View>
    );
}

export default React.memo(HomeComponent);

Избежать задержки на один кадр

Нам необходимо измерить ширину контейнера и, следовательно, подождать перед выводом некоторых элементов на экран. Если вы заранее знаете начальную ширину, то можете передать ее, и нам не нужно будет ждать ее измерения. Чаще всего это просто ширина окна.

Например, передадим initialLayout в TabView:

1
2
3
4
const initialLayout = {
    height: 0,
    width: Dimensions.get('window').width,
};

Вид вкладки по-прежнему будет реагировать на изменения размеров и соответствующим образом подстраиваться под такие вещи, как изменение ориентации.

Оптимизация большого количества маршрутов

Если у вас большое количество маршрутов, особенно изображений, это может сильно замедлить анимацию. Вместо этого можно отрисовывать ограниченное число маршрутов.

Например, чтобы отобразить только 2 маршрута с каждой стороны, сделайте следующее:

1
2
3
4
5
6
7
const renderScene = ({ route }) => {
    if (Math.abs(index - routes.indexOf(route)) > 2) {
        return <View />;
    }

    return <MySceneComponent route={route} />;
};

Избегайте рендеринга TabView внутри ScrollView

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

Используйте реквизиты lazy и renderLazyPlaceholder для рендеринга маршрутов по мере необходимости

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

Ссылки

Комментарии