Комплексный многоразовый компонент

Я хочу сделать что-то вроде этого

var App = React.createClass({
    render: function() {
        return (
            <CountryAutoComplete />
        )
    }
});

Различные приложения

var App2 = React.createClass({
    render: function() {
        return (
            <CountryAutoComplete />
        )
    }
});

Вот простой автозаполнение (не весь файл)

var AutoComplete = React.createClass({
    componentDidMount: function() {
        $(this.getDOMNode()).typeahead(this.props);
    },
    render: function() {
        return (
            <input type="text" class="typeahead" onChange={this.props.onChange} />
        );
    }
});

CountryAutoComplete был бы чем-то подобным, чтобы быть самодостаточным.

var CountryAutoComplete = React.createClass({
    search: function(country, process) {
        // Make an ajax call and return the data. No other components needed
        $.ajax({
            url: '/country' + '?search=' + country
        }).then(process);
    },
    render: function() {
        return (
            <AutoComplete onChange={this.props.onChange} source={this.search} />
        );
    }
});

Основываясь на документах Flux, похоже, что все, с вызовом API, должно пройти через

действия → API → Диспетчер → магазины → компонент

Это делает CountryAutoComplete привязанным к определенному приложению, потому что действия, Диспетчер и магазины специфичны для приложения. Каков наилучший способ сделать этот компонент многократным в разных приложениях?

Ответ 1

Вы не должны делать какие-либо вызовы ajax в компоненте автозаполнения (так как вы сказали, что хотите сделать его многоразовым). Я обычно помещаю все запросы запроса данных /api в отдельный модуль, который использует promises для предотвращения нескольких запросов

Итак, идея состоит в том, чтобы просто добавить ваш компонент автозаполнения к параметрам/данным из родительского компонента. Этот родительский компонент может сначала получить данные из хранилища, а также прослушать любые события изменений в этом хранилище и, при необходимости, обновить его состояние. Передайте этот this.state.options (или любое другое состояние, которое вы используете для параметров) в качестве опоры для автозаполнения. Когда пользователь что-то набирает, выпустите действие с запросом. Это действие по очереди должно вызывать API и диспетчер, обновлять хранилище и испускать событие изменения для хранилища. Ваш родительский компонент будет обновлять свое состояние соответственно, и это будет передаваться компоненту автозаполнения в качестве опоры.

Так что-то вроде этого:

var App = React.createClass({
    getInitialState: function() {
        return {
            // default results/data?
            data : Store.getResults('')
        };
    },
    storeChangeListener: function(newData) {
        this.setState({
            data: newData
        });
    },
    componentDidMount: function() {
        this.listenTo(Store, this.storeChangeListener);
    },
    onChange: function(query) {
        // on search change
        Actions.getResults(query);
    },
    render: function() {
        return (
            <AutoComplete data={this.state.data} onChange={this.onChange} />
        );
    }
});

И в магазине что-то вроде этого:

var countryAPI = require('./countryAPI')
var Store = {
    getResults: function(query) {
        // check cache if any? otherwise make call
        if(this.cache[query]) {
            return this.cache[query];
        } else {
            countryAPI.search(query).then(this.update);
        }
    },
    update: function(data) {
        AppDispatcher.dispatch({
            type: "DATA_FROM_SERVER",
            payload: {id: query, data: result}
        })
    },
    handleDataFromServer: function(action) {
        //store into cache/store
        this.cache[action.payload.id] = action.payload.result;
        this.emit("change"); // re-render app on whoever is listening to this store
    }
}

и ваш api, например

var countryAPI = {
    search: function(query) {
        // check to make sure this promise isnt called before
        if(!this.allPromises[query]) {
            this.allPromises[query] = $.ajax({
                url: '/country' + '?search=' + country
            })
        }
        return this.allPromises[query];
    }
}

Чтобы подвести итог, фактическая реализация API imo должна быть отделена от действий потока, они должны быть связаны только с конкретными вещами Web-API и просто позволяют действиям/хранилищам потока обрабатывать ответы отдельно как потоки данных:

Component --> Listens to Store
          --> Calls Load Action --> Show Pending State/Optimistic Updates --> Dispatcher --> Store --> changeEvent (Component will be listening and be updated)
          --> countryAPI.load() 
onLoadSuccess --> Dispatcher --> Store --> changeEvent --> Component
onLoadError   --> Dispatcher --> Store --> changeEvent --> Component