Что все неправильно понимают в модалах React Native


Руководство по освоению сложных модальных потоков React Native.

Использование модалов в React Native кажется вам немного мучительным? Вы не одиноки! Пытаться контролировать его открытое состояние и повторять код везде, где вы хотите его использовать, может быть довольно утомительно.

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

Я испытал это на собственном опыте во многих крупных компаниях, таких как Zé Delivery (от AB-InBev), Alfred Delivery, а теперь и в X-Team.

Но не стоит отчаиваться! Большинство людей думают, что это единственный путь. Тем не менее, в этой статье я объясню, как возникают проблемы и как элегантно с ними справиться, улучшив при этом опыт разработки. Это даже поможет вам использовать модалы внутри и вне компонентов React (например, в Sagas!).

Уроки этой статьи были заключены в эту библиотеку:
https://github.com/GSTJ/react-native-magic-modal.

Трудности «простого потока»

Представьте себе следующее. Вы работаете в Facebook, и команда разработчиков просит разработать «простой поток»:

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

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

Наконец, покажите модал «спасибо», поблагодарив пользователя за поддержку.

Это не так уж и надуманно, правда?

Четыре модала в глубину вызывают много вопросов со стороны разработчика. Где должна быть размещена эта логика? Как мы должны обрабатывать вывод из последнего модала? Как сохранить чистоту?

Можете ли вы представить, каким будет код? Вы видите себя пишущим сложную логику для обработки порядка модалов, убеждающимся, что каждый модал уже перешел в состояние «не виден» и имеющим тонны useStates на месте?

Рост проблемы

Представьте, что вы наконец-то сделали это, закончили поток, и команде разработчиков понравился результат! Теперь они хотят расширить этот поток, чтобы он появлялся только один раз, когда пользователю что-то нравится в первый раз, будь то пост, комментарий или продукт. Как бы вы к этому подошли?

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

Проходит несколько месяцев, и команда разработчиков видит сложность обработки лайков на каждом экране по-разному и хочет передать эту ответственность Redux Saga, который можно вызывать из любого места. Как показать модальное окно только при выполнении действия саги? Саги работают вне компонентов React.

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

Как этого избежать?

Для начала давайте разберемся с состоянием. Управление им — одна из самых важных вещей при работе с модалами.

Раскрытие внутренних свойств с помощью «useImperativeHandle»

Вкратце, useImperativeHandle позволяет вам раскрывать внутренние свойства через Ref. Если у вас есть модальный компонент, вы можете использовать useImperativeHandle для раскрытия его функций show и hide. Это означает, что он может заботиться о своем собственном состоянии, не делегируя его родительскому компоненту, например, с помощью props.

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

import React, { useState, useImperativeHandle } from 'react';
import { Text } from 'react-native';
import ModalContainer from 'react-native-modal';

export const ExampleModal = React.forwardRef((ref) => {
  const [isVisible, setIsVisible] = useState(false);

  const show = () => setIsVisible(true);
  const hide = () => setIsVisible(false);

  useImperativeHandle(ref, () => ({ hide, show }));

  return (
    <ModalContainer onBackdropPress={hide} isVisible={isVisible}>
      <Text>My awesome modal!</Text>
    </ModalContainer>
  );
});
Войти в полноэкранный режим Выход из полноэкранного режима

Это переход к лучшему.

Хотя это шаг в правильном направлении, сам по себе он не решает всех наших проблем. А именно:

  • Чтобы использовать его, нам нужно передать ref prop из хука useRef, что означает, что мы не можем вызвать show вне компонентов React.
  • Нам по-прежнему нужно инстанцировать компонент на каждом экране. Нет способа использовать его на нескольких экранах без повторения ExampleModal.

Внешнее раскрытие Ref компонента

Большинство людей этого не знают, но в React есть метод createRef, который можно использовать вне компонентов React. На самом деле, он даже есть в документации по React-Navigation для крайних случаев.

https://reactnavigation.org/docs/navigating-without-navigation-prop/

На практике гибкость, которую он дает, можно увидеть здесь:

import React, { useState, useImperativeHandle } from 'react';
import { Text } from 'react-native';
import ModalContainer from 'react-native-modal';

export const imperativeModalRef = React.createRef();

export const SmartExample = () => {
  const [isVisible, setIsVisible] = useState(false);

  const show = () => setIsVisible(true);
  const hide = () => setIsVisible(false);

  useImperativeHandle(imperativeModalRef, () => ({ hide, show }));

  return (
    <ModalContainer onBackdropPress={hide} isVisible={isVisible}>
      <Text>My awesome modal!</Text>
    </ModalContainer>
  );
};
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь imperativeModalRef может быть импортирован и использован где угодно, пока SmartExample находится в корне. Мы можем использовать show и hide из каждого компонента, функции или Saga.

Это решает большинство наших проблем, но одна все еще остается: Трудно управлять проектом с большим количеством модальных ссылок и модалов в корне.

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

Пройти лишнюю милю

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

Вот основная идея того, как ее использовать:

«Разговоры стоят дешево. Покажите мне код». — Линус Торвальдс.


import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { MagicModalPortal, magicModal } from 'react-native-magic-modal';

const ConfirmationModal = () => (
  <View>
    <TouchableOpacity onPress={() => magicModal.hide({ success: true })}>
      <Text>Click here to confirm</Text>
    </TouchableOpacity>
  </View>
);

const ResponseModal = ({ text }) => (
  <View>
    <Text>{text}</Text>
    <TouchableOpacity onPress={() => magicModal.hide()}>
      <Text>Close</Text>
    </TouchableOpacity>
  </View>
);

const handleConfirmationFlow = async () => {
  // We can call it with or without props, depending on the requirements of the modal.
  const result = await magicModal.show(ConfirmationModal);

  if (result.success) {
    return magicModal.show(() => <ResponseModal text="Success!" />);
  }

  return magicModal.show(() => <ResponseModal text="Failure :(" />);
};

export const MainScreen = () => {
  return (
    <View>
      <TouchableOpacity onPress={handleConfirmationFlow}>
        <Text>Start the modal flow!</Text>
      </TouchableOpacity>
      <MagicModalPortal />
    </View>
  );
};
Вход в полноэкранный режим Выход из полноэкранного режима

Пример с использованием react-native-magic-modal

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

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

Кроме того, он автоматически решает общие проблемы, связанные с модалами.

Знаете ли вы, что в React Native нельзя показывать два модала одновременно?

Даже если вы попытаетесь показать один модал сразу после другого, это, скорее всего, не удастся, поскольку последний модал все еще анимирует свое состояние «закрытия». К счастью, эта проблема уже решена на нашей стороне.

https://github.com/react-native-modal/react-native-modal/issues/30

Документация по React Native Magic Modal проста в понимании и даст вам фору. Вы можете узнать больше об этом здесь:

GSTJ / react-native-magic-modal

🦄 Модальная библиотека, которая может быть вызвана императивно из любого места!

Модальная библиотека, которая может быть вызвана императивно из любого места!

Использование модалов в React Native кажется вам немного мучительным? Пытаться контролировать его открытое состояние и повторять код везде, где вы хотите его использовать, может быть довольно утомительно.

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

Эта библиотека продуманно инкапсулирует сложные концепции, чтобы обеспечить плавный опыт при использовании модалов React внутри или вне компонентов (например, в Sagas!).

Ознакомьтесь с подробным объяснением ее концепций в статье на Medium.

IOS Android
yarn add react-native-magic-modal
Вход в полноэкранный режим Выход из полноэкранного режима

Сначала вставьте MagicModalPortal в…

Посмотреть на GitHub

Эта же логика уже была проверена в бою на крупных компаниях, над которыми я работал, таких как Zé Delivery (от AB-InBev), Alfred Delivery и X-Team.

Вклад принимается!

Спасибо за прочтение. Если вам понравилась статья, следите за мной на Linkedin и Github.

Оцените статью
devanswers.ru
Добавить комментарий