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

Связь между Android и React Native

В руководстве Интеграция с существующими приложениями и Native UI Components guide мы узнаем, как внедрить React Native в нативный компонент и наоборот. Когда мы смешиваем нативные и React Native компоненты, мы в конечном итоге столкнемся с необходимостью коммуникации между этими двумя мирами. Некоторые способы достижения этой цели уже упоминались в других руководствах. В этой статье мы обобщим имеющиеся методы.

Введение

React Native вдохновлен React, поэтому основная идея информационного потока схожа. Поток в React является однонаправленным. Мы поддерживаем иерархию компонентов, в которой каждый компонент зависит только от своего родителя и собственного внутреннего состояния. Мы делаем это с помощью свойств: данные передаются от родителя к его дочерним компонентам по принципу "сверху вниз". Если компонент-предок зависит от состояния своего потомка, необходимо передать обратный вызов, который будет использоваться потомком для обновления предка.

Та же концепция применима и к React Native. Пока мы создаем наше приложение исключительно в рамках фреймворка, мы можем управлять нашим приложением с помощью свойств и обратных вызовов. Но когда мы смешиваем React Native и нативные компоненты, нам нужны некоторые специфические, кросс-языковые механизмы, которые позволят нам передавать информацию между ними.

Свойства

Свойства — это самый простой способ межкомпонентного взаимодействия. Поэтому нам нужен способ передачи свойств как из native в React Native, так и из React Native в native.

Передача свойств из native в React Native

Вы можете передавать свойства в приложение React Native, предоставив пользовательскую реализацию ReactActivityDelegate в вашей основной активности. Эта реализация должна переопределить getLaunchOptions, чтобы вернуть Bundle с нужными свойствами.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class MainActivity extends ReactActivity {
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName()) {
    @Override
    protected Bundle getLaunchOptions() {
        Bundle initialProperties = new Bundle();
        ArrayList<String> imageList = new ArrayList<String>(Arrays.asList(
                "http://foo.com/bar1.png",
                "http://foo.com/bar2.png"
        ));
        initialProperties.putStringArrayList("images", imageList);
        return initialProperties;
    }
    };
}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class MainActivity : ReactActivity() {
    override fun createReactActivityDelegate(): ReactActivityDelegate {
        return object : ReactActivityDelegate(this, mainComponentName) {
            override fun getLaunchOptions(): Bundle {
                val imageList = arrayListOf("http://foo.com/bar1.png", "http://foo.com/bar2.png")
                val initialProperties = Bundle().apply { putStringArrayList("images", imageList) }
                return initialProperties
            }
        }
    }
}
ImageBrowserApp.tsx
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import React from 'react';
import { View, Image } from 'react-native';

export default class ImageBrowserApp extends React.Component {
    renderImage(imgURI) {
        return <Image source={{ uri: imgURI }} />;
    }
    render() {
        return (
            <View>
                {this.props.images.map(this.renderImage)}
            </View>
        );
    }
}

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

1
2
3
4
5
6
7
8
Bundle updatedProps = mReactRootView.getAppProperties();
ArrayList<String> imageList = new ArrayList<String>(Arrays.asList(
        "http://foo.com/bar3.png",
        "http://foo.com/bar4.png"
));
updatedProps.putStringArrayList("images", imageList);

mReactRootView.setAppProperties(updatedProps);
1
2
var updatedProps: Bundle = reactRootView.getAppProperties()
var imageList = arrayListOf("http://foo.com/bar3.png", "http://foo.com/bar4.png")

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

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

В настоящее время JS-функция componentWillUpdateProps компонента RN верхнего уровня не будет вызываться после обновления свойств. Однако вы можете получить доступ к новым пропсам в функции componentDidMount.

Передача свойств из React Native в native

Проблема раскрытия свойств нативных компонентов подробно рассмотрена в этой статье. Вкратце, свойства, которые должны быть отражены в JavaScript, должны быть раскрыты как метод сеттера, аннотированный @ReactProp, затем использовать их в React Native, как если бы компонент был обычным компонентом React Native.

Ограничения свойств

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

Хотя у нас есть возможность использовать межъязыковые обратные вызовы (описано здесь), эти обратные вызовы не всегда то, что нам нужно. Основная проблема заключается в том, что они не предназначены для передачи в качестве свойств. Скорее, этот механизм позволяет нам вызвать нативное действие из JS и обработать результат этого действия в JS.

Другие способы межъязыкового взаимодействия (события и нативные модули)

Как уже говорилось в предыдущей главе, использование свойств связано с некоторыми ограничениями. Иногда свойств недостаточно для управления логикой нашего приложения, и нам нужно решение, обеспечивающее большую гибкость. В этой главе рассматриваются другие методы коммуникации, доступные в React Native. Они могут быть использованы как для внутренней связи (между JS и нативными слоями в RN), так и для внешней связи (между RN и "чистой нативной" частью вашего приложения).

React Native позволяет выполнять кросс-языковые вызовы функций. Вы можете выполнять пользовательский нативный код из JS и наоборот. К сожалению, в зависимости от того, на какой стороне мы работаем, мы достигаем одной и той же цели разными способами. Для native — мы используем механизм событий для планирования выполнения функции-обработчика в JS, а для React Native мы напрямую вызываем методы, экспортируемые нативными модулями.

Вызов функций React Native из native (события)

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

События являются мощным инструментом, поскольку они позволяют нам изменять компоненты React Native без необходимости ссылки на них. Однако есть несколько подводных камней, на которые можно нарваться при их использовании:

  • Поскольку события могут быть отправлены откуда угодно, они могут привнести в ваш проект зависимости в стиле "спагетти".
  • События разделяют пространство имен, что означает, что вы можете столкнуться с некоторыми коллизиями имен. Коллизии не будут обнаружены статически, что затрудняет их отладку.
  • Если вы используете несколько экземпляров одного и того же компонента React Native и хотите различать их с точки зрения события, вам, скорее всего, придется ввести идентификаторы и передавать их вместе с событиями (в качестве идентификатора можно использовать reactTag нативного представления).

Вызов нативных функций из React Native (нативные модули)

Нативные модули — это классы Java/Kotlin, которые доступны в JS. Обычно на один JS-мост создается один экземпляр каждого модуля. Они могут экспортировать произвольные функции и константы в React Native. Они были подробно рассмотрены в этой статье.

Предупреждение

Все нативные модули используют одно и то же пространство имен. Следите за коллизией имен при создании новых модулей.

Комментарии