Циклическая зависимость возвращает пустой объект в React Native

У меня есть два активных компонента React (Alpha и Beta), которые переходят друг к другу; однако это вызывает циклическую зависимость, и React Native, похоже, не справляется с ними.

Требование бета-версии в Alpha отлично работает, но требование Alpha в бета-версии возвращает пустой объект. При попытке направить маршрут с недопустимым компонентом возникает ошибка.

Могут ли циклические зависимости работать в React Native? Если нет, как мне обойти это?

код

index.ios.js

'use strict';

var React = require('react-native');

var Alpha = require('./Alpha');

var {
    AppRegistry,
    NavigatorIOS,
    StyleSheet,
    Text,
    View,
} = React;

var ExampleProject = React.createClass({
    render() {
        return (
            <NavigatorIOS
                style={styles.container}
                initialRoute={{
                    component: Alpha,
                    title: Alpha.title,
                    wrapperStyle: styles.wrapper
                }} />
        );
    },
});

var styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: 'white'
    },
    wrapper: {
        paddingTop: 64
    }
});

AppRegistry.registerComponent('ExampleProject', () => ExampleProject);

Alpha.js

'use strict';

var React = require('react-native');
var Beta = require('./Beta');

var {
    StyleSheet,
    TouchableHighlight,
    View,
    Text
} = React;

var Alpha = React.createClass({
    statics: {
        title: 'Alpha'
    },

    goToBeta() {
        this.props.navigator.push({
            component: Beta,
            title: Beta.title,
            wrapperStyle: styles.wrapper
        });
    },

    render() {
        return (
            <View>
                <TouchableHighlight style={styles.linkWrap}
                    onPress={this.goToBeta}>
                    <Text>Go to Beta</Text>
                </TouchableHighlight>
            </View>
        );
    }
});

var styles = StyleSheet.create({
    linkWrap: {
        flex: 1,
        alignItems: 'center',
        padding: 30
    },
    wrapper: {
        paddingTop: 64
    }
});

module.exports = Alpha;

Beta.js

'use strict';

var React = require('react-native');
var Alpha = require('./Alpha');

var {
    StyleSheet,
    TouchableHighlight,
    View,
    Text
} = React;

var Beta = React.createClass({
    statics: {
        title: 'Beta'
    },

    goToAlpha() {
        this.props.navigator.push({
            component: Alpha,
            title: Alpha.title,
            wrapperStyle: styles.wrapper
        });
    },

    render() {
        return (
            <View>
                <TouchableHighlight style={styles.linkWrap}
                    onPress={this.goToAlpha}>
                    <Text>Go to Alpha</Text>
                </TouchableHighlight>
            </View>
        );
    }
});

var styles = StyleSheet.create({
    linkWrap: {
        flex: 1,
        alignItems: 'center',
        padding: 30
    },
    wrapper: {
        paddingTop: 64
    }
});

module.exports = Beta;

Ответы

Ответ 1

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

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


С import и export

Используйте ключевое слово export для экспорта Alpha и Beta из их соответствующих файлов и используйте ключевое слово import для их импорта:

// Alpha.js
import Beta from './Beta';

var Alpha = React.createClass({
    /* ... */
});
export default Alpha;

Это работает, потому что во время выполнения ключевое слово export создает промежуточный объект (эквивалентный module.exports в CommonJS) и присваивает ему свойство с именем default со значением Alpha. Итак, вышеприведенный оператор export концептуально похож на этот:

module.exports.default = Alpha;

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

import Alpha from './Alpha';

var ExampleProject = React.createClass({
    render() {
        return (
            <NavigatorIOS
                style={styles.container}
                initialRoute={{
                    component: Alpha,
                    title: Alpha.title,
                    wrapperStyle: styles.wrapper
                }}
            />
        );
    },
});

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

var AlphaExports = require('./Alpha');

var ExampleProject = React.createClass({
    render() {
        return (
            <NavigatorIOS
                style={styles.container}
                initialRoute={{
                    component: AlphaExports.default,
                    title: AlphaExports.default.title,
                    wrapperStyle: styles.wrapper
                }}
            />
        );
    },
});

С require()

Используя CommonJS require, у вас есть другие варианты:

Подход 1: ленивая загрузка

Альфа и бета не нужны друг другу, пока пользователь не перейдет с одной сцены на другую. Чтобы приложение достигло этого состояния, Alpha, должно быть, определила его экспорт (т.е. module.exports = Alpha должен был запустить приложение для отображения альфа-компонента). Таким образом, когда пользователь переходит на сцену, отображая бета-версию, для Beta безопасно требовать Alpha, и поэтому в этот момент можно безопасно требовать бета-версию.

// Alpha.js
var Alpha = React.createClass({
    goToBeta() {
        // Lazily require Beta, waiting until Alpha has been initialized
        var Beta = require('./Beta');

        this.props.navigator.push({
            component: Beta,
            title: Beta.title,
            wrapperStyle: styles.wrapper
        });
    }
});

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

Подход 2: Единый модуль

Другим решением является установка Alpha и Beta в один и тот же JS файл для удаления цикла между модулями. Затем вы экспортируете оба компонента из нового мегамодуля.

// AlphaBeta.js
var Alpha = React.createClass({...});
var Beta = React.createClass({...});
exports.Alpha = Alpha;
exports.Beta = Beta;

Чтобы потребовать его:

// index.js
var {Alpha, Beta} = require('./AlphaBeta');