Итак, вы решили, что вы следующий Цукерберг, и готовы создать следующий Facebook. Большой! Только одна проблема: вам нужно создать сайт. К счастью, у вас есть некоторый опыт веб-дизайна и вы разбираетесь в JavaScript. Возможно, вы работали с такими фреймворками, как Angular, Ember или React. Но ваши более миллиарда будущих пользователей заслуживают качественной интерфейсной архитектуры, которая работает лучше, чем тот сценарий захвата, который вы написали в средней школе, который меняет цвет фона каждые 5 миллисекунд.

Введите Fluxible, фреймворк, разработанный Yahoo! и построен на основе NodeJS и ReactJS от Facebook, который абстрагирует большую часть функций, необходимых для создания приложения isomorphic flux, так что вы можете создать его быстро и легко. Возможно, это было просто набор модных словечек, но в этом уроке я объясню все это, объясню, почему изоморфный поток хорошо работает для вашей интерфейсной веб-архитектуры, и предоставлю пошаговые инструкции, которые помогут вам построить безусловный успех, который станет вашим следующим Facebook.

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

Почему я должен использовать Fluxible

«Ага, почему я не могу просто склеить несколько HTML-файлов и выложить их где-нибудь на сервере Apache и называть это готовым?» Что ж, вы могли бы, и вы также можете использовать Squarespace для создания своего сайта. Когда вы создаете сайт с помощью Fluxible, вы выходите за рамки того, что требуется для группы веб-страниц. Вы создаете не веб-сайт, а веб-приложение.

Fluxible создает одностраничные приложения

Чтобы понять важность одностраничных приложений, вот урок истории! Когда Интернет начинался, это была просто куча html-файлов, запрашиваемых веб-браузерами. Затем мы стали немного умнее и сказали: «Эй, а что, если бы вместо того, чтобы хранить кучу этих файлов на наших серверах, наши серверы динамически создавали HTML-документ и делали вид, будто это HTML-файл. С браузером все то же самое ". Помня об этом, мы смогли создать страницы, которые сами заполнялись данными, и формы, которые могли публиковать информацию. Но все по-прежнему рассматривалось как отдельные страницы.

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

Одностраничные приложения (SPA) больше похожи на создание обычных программ, чем на веб-сайт. Когда страница впервые загружается браузером, все базовые данные для фреймворка отправляются, и вместо перезагрузки страницы и повторного получения всех этих данных, когда пользователь щелкает ссылку или запрашивает новую информацию, SPA запросит сервер только для той информации, которая ему нужна.

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

Тем не менее, существует множество фреймворков, которые позволяют создавать SPA (React, Other Flux Implementations, Angular, Meteor, Backbone, Ember…), так что теперь давайте посмотрим, что отличает Fluxible от них.

Выполнить рендеринг на стороне сервера легко

Большая проблема, связанная с SPA, заключается в том, что они используют JavaScript для создания страницы в веб-браузере пользователя. Это может вызвать снижение производительности для пользователя в зависимости от мощности его / ее компьютера, и проблема с производительностью не является проблемой пользователя. Представьте, если бы Google сказал: «Эй, мы знаем, что загрузка Gmail загружается медленно, но у нас есть отличный способ решения этой проблемы. Получите лучший компьютер! Желательно такую, где у вас нет множества панелей инструментов, которые вы никогда не используете, занимая половину экрана в вашем браузере! "

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

Решением обеих этих проблем является рендеринг на стороне сервера. Вместо того, чтобы сервер сказал: «Вот полная веб-страница. Позвони мне снова, если захочешь чего-то нового », как это делается в многостраничных приложениях, или скажи:« Вот страница с кучей JavaScript. Вы понимаете это », как это происходит со многими приложениями SPA, рендеринг на стороне сервера позволяет серверу сказать:« Эй, вы знаете весь этот JavaScript, который я бы попросил вас запустить, чтобы создать HTML для загрузки нашего первого представления? Да, я уже запустил. Вот HTML! О, и вот вам набор JavaScript, который вы можете запустить после загрузки страницы ». Теперь мы получаем преимущества одностраничного приложения, не снижая производительности при начальной загрузке и не сбивая с толку сканеры.

Плавкий изоморфен

Но рендеринг на стороне сервера создает новую проблему. Теперь я мог бы рендерить свой сайт либо на клиенте, либо на сервере, поэтому мне нужно писать другой код в зависимости от среды, верно? Но не с Fluxible. Он изоморфен, что означает, что тот же код, который написан для сервера, используется на клиенте, поэтому разработка может быть намного быстрее!

Fluxible - это подключаемый

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

Хорошо, а что такое Flux?

Прежде чем вы начнете использовать Fluxible, очень важно понять шаблон проектирования Flux. Проще говоря, Flux - это шаблон проектирования от Facebook, который организует поток информации по всему приложению в одном направлении. Сложно поставить:

Шаблон проектирования flux состоит из четырех элементов: Действия, Диспетчер, Магазины и Представления. Информация в приложении передается именно в таком порядке. Вот и все. Вы можете узнать больше о Flux на странице Facebook Обзор Flux.

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

Действия

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

Диспетчер

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

магазины

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

Просмотры

Представления обрабатывают отображение информации для пользователя. Хотя представления потока могут быть реализованы во многих фреймворках, поскольку поток принадлежит Facebook, представления обычно реализуются с использованием компонентов React от Facebook (далее мы будем называть представления «компонентами»). Это остается верным с Fluxible. Компоненты могут прослушивать различные хранилища и обновляться при обновлении хранилищ. Они также могут запускать действия.

Услуги

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

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

Начало работы - установка необходимых компонентов

Предполагая, что вы начинаете с чистого листа, вам необходимо установить несколько вещей:

Во-первых, вся система построена на NodeJS, серверном языке, использующем синтаксис JavaScript. Установка отличается в зависимости от используемой вами ОС, поэтому вы можете получить инструкции по установке на вашем компьютере здесь.

Выполните следующие команды, чтобы убедиться, что вы успешно установили и NodeJS, и npm (диспетчер пакетов узлов), которые должны были быть установлены вместе с NodeJS.

$ node -v
$ npm -v

Если все работает, вы должны увидеть напечатанные номера версий.

Также убедитесь, что вы установили git. Запустите это, чтобы убедиться, что он правильно установлен:

$ git --version

Создание вашего проекта

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

Однако для целей этого руководства я собираюсь использовать модифицированную версию того, что создается генераторами. Генераторы создают проект в синтаксисе ES6, который представляет собой замечательный новый синтаксис для JavaScript, который позволяет удобно иметь строго типизированные переменные и полиморфизм. Это что-то отличное для проверки, но это руководство посвящено гибкости, а не ES6, поэтому я собираюсь изменить все на синтаксис, с которым, вероятно, все знакомы.

Вы можете использовать генераторы и следовать за ES6, или я также создал ES5-версию сгенерированного кода в репозитории github, который вы можете разветвить или клонировать.

Использование генератора

Все еще пользуетесь генератором? Прохладный. Генератор работает на Йомене. Генераторы Yeoman помогают настроить фреймворки проекта, чтобы у вас было что-то, что можно было бы создать прямо из коробки. Чтобы установить Yeoman, запустите:

$ npm install -g yo

Обратите внимание, что в зависимости от настроек безопасности вашей системы вам может потребоваться предоставить разрешение на глобальную установку пакета npm (на компьютерах Mac и Linux), запустив

$ sudo npm install yo

Теперь давайте установим генератор Fluxible.

$ npm install -g generator-fluxible

Если это удалось, то пора начинать наш проект. Используйте командную строку / терминал, чтобы перейти в папку, в которой вы хотите разместить свои проекты. У меня есть папка в корневом каталоге под названием projects, и, конечно же, мы создадим папку для нашего сайта под названием «the-next-facebook».

$ cd /projects
$ mkidr the-next-facebook
$ cd the-next-facebook

Теперь используйте Yeoman для создания проекта:

$ yo fluxible

Это сгенерирует проект и установит все его зависимости. Теперь убедитесь, что все работает:

$ npm run dev

Перейдите к http: // localhost: 3000 в своем веб-браузере, и вы должны увидеть домашнюю страницу с возможностью выбора страницы с информацией.

Использование моего репозитория шаблонов

А, тогда с помощью моего репо? Хорошо, клонируйте репо на https://github.com/jaxoncreed/fluxible-template. Не забудьте изменить происхождение на свое собственное репо.

$ git clone https://github.com/jaxoncreed/fluxible-template.git
$ git remote remove origin
$ git remote add origin [PATH TO YOUR OWN REPO]

Теперь запустите npm install, чтобы установить все необходимые пакеты.

$ npm install

А теперь просто убедитесь, что все работает, запустив проект:

$ npm run dev

Перейдите к http: // localhost: 3000 в своем веб-браузере, и вы должны увидеть домашнюю страницу с возможностью выбора страницы с информацией.

Все, что осталось сделать, это обновить файлы readMe и package.json, указав имя, соответствующее вашему проекту.

Начните создание сайта с удаления ненужных вещей (git diff)

Большой! Теперь, когда мы прошли через все это, пришло время наконец приступить к созданию нашего революционного сайта. Наша цель проста: узурпировать крупнейшую в мире компанию социальных сетей, создав The Next Facebook. Но сначала генератор создал вещи, которые нам не нужны.

Не поймите меня неправильно, текущей версии нашего сайта, созданной генераторами (на фото выше), достаточно, чтобы получить доступ к Y Combinator, но это не то, к чему мы стремимся. Итак, давайте удалим все ненужное! (просмотрите git diff, чтобы увидеть, что я удалил)

Создание собственного маршрута (git diff)

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

Давайте начнем с небольшого колорита, добавив заголовок «Добро пожаловать в The Next Facebook!» Этот заголовок является очень ценным предложением для нашего сайта, поэтому мы хотим, чтобы он отображался на каждой странице.

/components/Application.js

...
return (
    <div>
        <h1>Welcome to The Next Facebook!</h1>
        <Handler />
    </div>
);
...

Application.js - это самый высокий уровень нашего приложения, который обрабатывает представления различных маршрутов (которые вставляются в Handler). В результате всегда будет отображаться любой компонент или элемент HTML в функции рендеринга приложения.

Теперь давайте настроим маршрут для домашней страницы. Мгновенный захват пользователей является обязательным условием, поэтому мы направим их прямо на страницу входа в систему.

/configs/routes.js

module.exports = {
    login: {
        path: '/',
        method: 'get',
        page: 'login',
        title: 'Log In',
        handler: require('../components/Login')
    }
};

Все ваши маршруты определены в routes.js. Здесь мы говорим, что когда путь пуст, fluxible должен отображать компонент, расположенный по адресу «../components/Login».

Теперь нам просто нужно создать компонент Login.js React для обработки этого маршрута. Наша страница входа в систему довольно инновационная. Чтобы достичь максимального соответствия продукта рынку, мы хотим избежать барьера для входа, вызванного фактической необходимостью создания учетной записи. Вместо этого на The Next Facebook пользователи просто вводят имя пользователя, и они входят!

/components/Login.js

var React = require('react');  
var Login = React.createClass({  
    render() {  
        return (  
            <div>  
                <h2>Log In</h2>  
                <form>  
                    <label for="username">Username:</label>  
                    <input type="text" />  
                    <input id="username" type="submit" value="Log In" />  
                </form>  
            </div>
        );  
    }  
});  
module.exports = Login;

Здесь мы только что создали простой компонент React, который будет служить нашей страницей входа. Если вы еще не знакомы с React, я настоятельно рекомендую вам пройти обучение, подобное этому. Но если кратко пояснить, компоненты React по сути похожи на создание ваших собственных HTML-тегов. Показанная выше функция render определяет все элементы HTML, которые входят в этот компонент. Есть и другие функции, которые позволяют писать JavaScript для определения поведения вашего компонента. Компоненты могут быть вложены друг в друга, поэтому, когда вы запускаете этот сайт и переходите к http: // localhost: 3000, маршрутизатор сообщает компоненту Приложение, что мы в настоящее время находимся на путь с обработчиком в ../components/Login. Компонент Приложение затем вложит компонент Вход в нужное место для рендеринга страницы.

Ссылка на другие маршруты (git diff)

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

/configs/routes.js

...    
    feed: {  
        path: '/feed',  
        method: 'get',  
        page: 'feed',  
        title: 'Your Feed',  
        handler: require('../components/Feed')  
    }
...

/components/Feed.js

var React = require('react');  
var Feed = React.createClass({  
    render() {  
        return (  
            <div>  
                <h2>Your Constant Feed of Content</h2>  
                <p>How original</p>  
            </div> 
        );  
    }  
});  
module.exports = Feed;

Хорошо, мы знали, как это сделать раньше, но теперь давайте создадим панель навигации.

/components/Application.js

...
var NavBar = require('./NavBar'); 
...  
    render: function() {  
        var Handler = this.props.currentRoute.get('handler');  
        return (  
            <div>  
                <h1>Welcome to The Next Facebook!</h1>  
                <NavBar />
                <Handler />  
            </div>  
        );
    },
...

/components/NavBar.js

var React = require('react');  
var NavLink = require('fluxible-router').NavLink; 
var NavBar = React.createClass({  
    render() {  
        return (  
            <p>  
                <NavLink href="/">Home</NavLink>  
                <span> - </span>  
                <NavLink href="/feed">Your Feed</NavLink>   
            </p>  
        );  
    }  
});  
module.exports = NavBar;

Обратите внимание, что ссылки в NavBar.js используют NavLinks, а не обычный тег «a». Вы, конечно, могли бы достичь того же эффекта, что и выше, с помощью тега «a», но на самом деле это будет еще один вызов сервера и перезагрузка всей страницы, что сводит на нет цель одностраничного приложения. Это будет менее эффективно, и вы не сможете воспользоваться всеми преимуществами, которые дает СПА.

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

Следовательно, вы можете достичь того же результата, просто вызвав NavigateAction в JavaScript:

/components/NavBar.js

var React = require('react');  
var NavigateAction = require('fluxible-router').navigateAction;  
var NavBar = React.createClass({  
    contextTypes: {  
        executeAction: React.PropTypes.func.isRequired  
    },  
    goToHref: function(href) {  
        //This is how you execute an action. We'll go into that in detail in the next step 
        this.context.executeAction(NavigateAction, { 
            method: "GET",  
            url: href  
        });
    },
    render: function() {  
        return (  
            <p>  
                <span onClick={this.goToHref.bind(this, "/")}>Home</span> 
                <span> - </span>  
                <span onClick={this.goToHref.bind(this, "/feed")}>Your Feed</span>;
            </p>  
        );  
    }  
});  
module.exports = NavBar;

Использование собственных действий и магазинов

Пришло время реализовать полный поток потока в нашем проекте. Напомним, что в потоке информация течет в одном направлении: Компонент - ›Действие -› Диспетчер - ›Магазин -› Компонент. Диспетчер абстрагируется от fluxible, и мы уже знаем, как создавать компоненты, поэтому давайте узнаем, как реализовывать действия и хранилища.

Это прекрасная возможность завершить вход в систему The Next Facebook. Для этого нам нужно выполнить действие для входа пользователя в систему (действие для выхода из системы не требуется. Жизненно важно, чтобы удержание пользователей сайта было по-настоящему вирусным. Почему вы позволяете им выходить из системы?) И Магазин для отслеживать пользователя.

Создать и выполнить действие (git diff)

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

/components/Login.js

var React = require('react');  
var loginAction = require('../actions/login');
var Login = React.createClass({  
    contextTypes: {  
        executeAction: React.PropTypes.func.isRequired  
    },  
    handleLogin: function(element) {  
        element.preventDefault();  
        this.context.executeAction(loginAction, {  
            username: React.findDOMNode(this.refs.username).value.trim()  
        });  
    }, 
    render: function() {  
        return (  
            <div>  
                <h2>Log In</h2>  
                <form onSubmit={this.handleLogin}>  
                    <label for="username">Username:</label>  
                    <input id="username" type="text" ref="username" />  
                    <input type="submit" value="Log In" />  
                </form>  
            </div>  
        );  
    }  
});  
module.exports = Login;

Здесь много чего добавлено. Я начну с объяснения частей React. Атрибут onSubmit - это версия атрибута onsubmit в HTML в React. Он запускает созданную нами функцию и передает элемент формы в качестве атрибута. По умолчанию элемент формы перезагружает страницу при отправке, но поскольку мы создаем одностраничное приложение, мы не хотим, чтобы страница когда-либо перезагружалась. Чтобы решить эту проблему, мы вызываем preventDefault () для переданного элемента. Кроме того, мы можем получить значение текстового поля, используя атрибут ref React, чтобы найти текстовое поле.

Теперь о гибких элементах. Контекст включает функцию executeAction, потому что мы включили ее в contextTypes. Вот как мы запрашиваем действия. ExecuteAction принимает два параметра. Действие (функция, которая требовалась выше) и Полезная нагрузка. Полезная нагрузка содержит всю информацию, которую необходимо передать от компонента к действию. В этом примере мы выполняем действие из «/actions/login.js» и передаем объект с нашим именем пользователя в качестве полезной нагрузки.

Компонент обновлен, так что теперь пора создать журнал действий:

/actions/login.js

module.exports = function (context, payload, callback) {  
    console.log("Inside the Log In Action!");  
    console.log("Username: " + payload.username);  
    context.dispatch('USER_LOGGED_IN', {  
        username: payload.username  
    });
    callback();  
};

Все действия принимают три параметра. Во-первых, это контекст. Контекст представляет собой actionConext и содержит несколько полезных функций. Одна из этих функций - отправка. Dispatch вызывает диспетчера, и мы знаем из потока потока, что диспетчер передаст предоставленные данные в хранилище. В этом примере мы вызываем диспетчер, сообщая ему отправить сообщение USER_LOGGED_IN для любого хранилища, которое ожидает обработки этого сообщения, и мы передаем свое имя пользователя в качестве данных для отправки во втором параметре.

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

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

Создание собственного магазина (git diff)

Пришло время объединить все это вместе с магазином:

/stores/UserStore.js

var createStore = require('fluxible-app/utils/createStore');  
var UserStore = createStore({  
    storeName: "UserStore",  
    handlers: {  
        "USER_LOGGED_IN": "handleUserReceived"  
    },  
    initialize: function(dispatcher) {  
        this.username = null;  
    },  
    handleUserReceived: function(payload) {  
        this.username = payload.username;  
        this.emitChange();  
    },  
    getUsername: function() {  
        return this.username;  
    },  
    dehydrate: function() {  
        return {  
            username: this.username  
        };  
    },  
    rehydrate: function(state) {  
        this.username = state.username;  
    }  
});  
module.exports = UserStore;

Когда действие входа вызывает context.dispatch («USER_LOGGED_IN», полезная нагрузка), диспетчер инициирует любые хранилища, у которых есть обработчик, чтобы принять «USER_LOGGED_IN». В этом примере вы можете видеть, что мы действительно обрабатываем «USER_LOGGED_IN», и это отображается на имя функции, которая будет обрабатывать запрос (handleUserReceived).

В handleUserReceived мы обновляем соответствующую переменную. Не забудьте запустить emitChange (), когда закончите обновление магазина. Если этого не сделать, ни один из компонентов не будет обновлен.

getUsername - это просто стандартный метод получения для использования компонентов, которые прослушивают хранилище.

Обезвоживание и регидратация более интересны. Когда приложение flux участвует в рендеринге на стороне сервера, переменные магазина обновляются на сервере, но те же переменные должны совпадать на клиенте. Чтобы отправить эти переменные клиенту, магазин «обезвоживается», преобразуя все важные переменные в JSON. Когда одно и то же хранилище загружается на клиенте, ему необходимо получить текущее значение всех его переменных. При регидрате JSON, отправляемый клиенту, поступает через «состояние», и тогда задача заключается в установке всех переменных магазина на основе этого JSON.

Все магазины также должны быть зарегистрированы в приложении. Итак, давайте сделаем это:

/app.js

var Fluxible = require('fluxible');  
var Application = require('./components/Application');  
var ApplicationStore = require('./stores/ApplicationStore');  
var RouteStore = require('./stores/RouteStore');  
var UserStore = require('./stores/UserStore');
// create new fluxible instance  
var app = new Fluxible({  
    component: Application  
});
// register stores  
app.registerStore(RouteStore);  
app.registerStore(ApplicationStore);  
app.registerStore(UserStore); 
module.exports = app;

И, наконец, мы можем обновить один из наших компонентов, чтобы использовать информацию из UserStore. Чтобы продемонстрировать силу гибкости, давайте обновим NavBar, а не компонент Login. Поскольку информация находится в магазине, любой компонент может прослушивать любой магазин. Не нужно беспокоиться о передаче информации между компонентами. Один и тот же принципал связан с действиями и хранилищами: любой компонент может прослушивать любое хранилище, любое действие может запускаться любым компонентом, и любое хранилище может обрабатывать информацию, отправляемую из любого действия.

/components/NavBar.js

var React = require('react');  
var NavLink = require('fluxible-router').NavLink;  
var connectToStores = require("fluxible-addons-react").connectToStores; 
var UserStore = require('../stores/UserStore');
var NavBar = React.createClass({  
    render() {  
        var loggedInMessage = "";  
        if (this.props && this.props.username) {  
            loggedInMessage = (<span> - Welcome <strong>{this.props.username}</strong></span>);  
        }
        return (  
            <p>  
                <NavLink href="/">Home</NavLink>  
                <span> - </span>  
                <NavLink href="/feed">Your Feed</NavLink>  
                {loggedInMessage}
            </p>  
        );  
    }  
});
module.exports = connectToStores(  
    NavBar,  
    [UserStore],  
    function (context, props) {
        return {
            username: context.getStore(UserStore).getUsername()  
        }
    } 
);

connectToStores - это один из нескольких способов заставить компонент прослушивать магазин. Он принимает 3 параметра: компонент, который необходимо подключить, массив хранилищ, к которым компонент должен подключиться, и функцию для установки свойств на основе информации, предоставленной в магазине. Эта информация помещается в реквизиты элемента React и может обрабатываться как любые другие реквизиты (так что да, componentWillReceiveProps действительно работает).

Другие советы и обзор

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

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

Запуск действия при загрузке страницы (git diff)

Давайте настроим действие, магазин и компонент для фида. Во-первых, давайте создадим фиктивные данные для заполнения ленты ... Боже мой, похоже, вы знакомы с довольно историческими гигантами!

/dummyData/posts.json

[  
    {  
        "user": "MararetThatcher",  
        "type": "text",  
        "message": "OMG. I'm totes cheesed off with Argentina."  
    },  
    {  
        "user": "RichardNixon",  
        "type": "text",  
        "message": "Just got totally chirped by Frost. FML"  
    },  
    {  
        "user": "GeorgeWBush",  
        "type": "image",  
        "url": "http://4.bp.blogspot.com/-Woa9TTFy77U/VaaJtw9prVI/AAAAAAAACV4/SWvAV0x96c0/s1600/GWBShower.jpg",  
        "message": "Hey guys, just made a new painting. Hope you like."  
    },  
    {  
        "user": "JPMorgan",  
        "type": "text",  
        "message": "Swag money! Just bailed out the US gov doe!"  
    }  
]

Теперь давайте добавим действие для получения этих данных.

/actions/getPosts.js

var posts = require('../dummyData/posts.json');
module.exports = function (context, payload, callback) { 
    context.dispatch('POSTS_RECEIVED', {  
        posts: posts  
    });
    callback();  
};

Теперь в магазине:

/stores/FeedStore.js

var createStore = require('fluxible-app/utils/createStore');  
var FeedStore = createStore({  
    storeName: "FeedStore",  
    handlers: {  
        "POSTS_RECEIVED": "handlePostsReceived"  
    },  
    initialize: function(dispatcher) {  
        this.posts = [];  
    },  
    handlePostsReceived: function(payload) { 
        this.posts = payload.posts;  
        this.emitChange();
    },  
    getPosts: function() {  
        return this.posts;  
    },  
    dehydrate: function() {  
        return {  
            posts: this.posts  
        };  
    },  
    rehydrate: function(state) {  
        this.posts = state.posts;  
    }  
});  
module.exports = FeedStore;

Зарегистрируйте магазин в приложении

var Fluxible = require('fluxible');  
var Application = require('./components/Application');  
var ApplicationStore = require('./stores/ApplicationStore');  
var RouteStore = require('./stores/RouteStore');  
var UserStore = require('./stores/UserStore');
var FeedStore = require('./stores/FeedStore');
// create new fluxible instance  
var app = new Fluxible({  
    component: Application  
});
// register stores  
app.registerStore(RouteStore);  
app.registerStore(ApplicationStore);  
app.registerStore(UserStore);
app.registerStore(FeedStore);
module.exports = app;

И, наконец, обновите компонент, чтобы отобразить информацию из магазина.

/components/Feed.js

var React = require('react');  
var connectToStores = require("fluxible-addons-react").connectToStores;
var FeedStore = require('../stores/FeedStore');
var Feed = React.createClass({  
    render() {
        return (  
            <div>  
                <h2>Your Constant Feed of Content</h2>  
                {  
                    this.props.posts.map(function(post) {  
                        var content;  
                        if (post.type === "text") {  
                            content = (  
                                <span>  
                                    <p>{post.message}</p>  
                                </span>  
                            )  
                        } else if (post.type === "image") {  
                            content = (  
                                <span>  
                                    <img src={post.url} />  
                                    <p>{post.message}</p>  
                                </span>  
                            )  
                        }  
                        return (  
                            <span>  
                                <hr />  
                                <p><strong>Post by: {post.user}</strong></p>  
                                {content}  
                            </span>  
                        )  
                    })  
                }
            </div> 
        );  
    }  
});
module.exports = connectToStores(  
    Feed,  
    [FeedStore],
    function(context, props) {
        return {
            posts: context.getStore(FeedStore).getPosts()  
        }
    } 
);

Прохладный. Если что-то из этого кажется вам незнакомым, прочтите предыдущий раздел еще раз.

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

/configs/routes.js

feed: {  
    path: '/feed',  
    method: 'get',  
    page: 'feed',  
    title: 'Your Feed',  
    handler: require('../components/Feed'),  
    action: require('../actions/getPosts')
}

Использование подключаемых модулей и служб с Fetchr (git diff)

Итак, у нас есть фид, но в реальном мире мы бы не использовали фиктивные данные. Вероятно, будет база данных или внешний API для загрузки данных из фида.

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

Сервисы используют плагин fluxible fetchr. Итак, сначала нам нужно установить fetchr и загрузить плагин в app.js.

$ npm install fluxible-plugin-fetchr --save

/app.js

var Fluxible = require('fluxible');  
var Application = require('./components/Application');  
var ApplicationStore = require('./stores/ApplicationStore');  
var RouteStore = require('./stores/RouteStore');  
var UserStore = require('./stores/UserStore');
var FeedStore = require('./stores/FeedStore');
var fetchr = require('fluxible-plugin-fetchr');
var fetchrInstance = fetchr({
    xhrPath: '/api'
});
// create new fluxible instance  
var app = new Fluxible({  
    component: Application  
});
app.plug(fetchrInstance);
// register stores  
app.registerStore(RouteStore);  
app.registerStore(ApplicationStore);  
app.registerStore(UserStore);
app.registerStore(FeedStore);
module.exports = app;

Теперь давайте создадим сервис для получения всех сообщений. Сервисы разработаны в соответствии с шаблоном проектирования RESTful, что означает, что они основаны на ресурсах. Каждая услуга может представлять собой что-то, что поможет вам получить ресурс и управлять им. У гибких сервисов есть четыре возможных функции: чтение (получение), создание, обновление и удаление. Вам не обязательно использовать все функции службы, и в большинстве случаев вы, вероятно, не будете этого делать.

Для The Next Facebook нам нужно только «прочитать», поэтому давайте создадим сервис:

/services/PostService.js

var posts = require('../dummyData/posts.json');
module.exports = {
    name: "posts",
    read: function(req, resource, params, config, callback) {
        // We would do some kind of database call here.
        callback(null, posts);
    }
}

Как видите, сервис - это просто объект. Для его идентификации требуется имя, а также различные функции CRUD (создание, получение, обновление, удаление). Для простоты этого руководства мы на самом деле не выполняем вызов базы данных, но в реальной производственной среде фиктивные данные, очевидно, исчезли бы.

Так же, как и магазины, услуги необходимо регистрировать. Но, в отличие от магазинов, они не будут зарегистрированы в app.js, потому что app.js используется совместно клиентом и сервером. Мы можем зарегистрировать их в server.js.

/server.js

...
var debug = debugLib('fluxible-template');
/* Regeister Services */
app.getPlugin('FetchrPlugin').registerService(require('./services/PostService'));
var server = express();
server.use('/public', express.static(path.join(__dirname, '/build')));
server.use(compression());
...

А теперь мы можем изменить наше действие «getPosts», чтобы использовать только что созданный сервис.

/actions/getPosts.js

module.exports = function (context, payload, callback) {
    context.service.read('posts', {}, {}, function(err, posts) {
        if (err) {
            callback(err);
        } else {
            context.dispatch('POSTS_RECEIVED', {  
                posts: posts  
            });
            callback();  
        }
    });
};

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

Наконец, давайте соберем все это вместе, объяснив все в службе.

{
    name: "myServiceName",
    create: function(req, resource, params, body, config, callback) {},
    read: function(req, resource, params, config, callback) {},
    update: function(req, resource, params, body, config, callback) {},
    delete: function(req, resource, params, config, callback) {}
}

Вот сервис со всеми возможными атрибутами, который не отвлекает. Атрибут name - это просто идентификатор этой службы, и он может быть любым. Все атрибуты CRUD имеют различные параметры, которые могут быть полезны при написании вашего сервиса:

  • req: необработанный объект запроса, представляющий запрос, полученный сервером. Если вы когда-нибудь работали с экспрессом, значит, вы уже привыкли к этому объекту. Он содержит всю информацию, которая может вам понадобиться по запросу.
  • ресурс: просто строка, представляющая службу, которая вызвала эту функцию. В этом примере это будет «myServiceName». Это полезно, если у вас есть функция, которая может обслуживать несколько служб.
  • params: параметры, которые были продиктованы действием, вызвавшим эту службу. Вы также можете думать об этом как о параметрах url в обычных HTTP-запросах, но если вы посмотрите на то, как fluxible делает запросы, вы увидите, что он не соответствует тем же стандартам с вопросительными знаками для параметров url.
  • body: тело запроса, которое было продиктовано действием, вызвавшим эту службу. Обратите внимание, что только create и update имеют параметр body. Тело - это фактический ресурс, который вы хотите создать или обновить, поэтому для чтения или удаления нет смысла использовать тело.
  • config: этот объект также был передан из действия. Он содержит некоторую информацию о конфигурации, если она указана в действии.
  • обратный вызов: вызовите эту функцию, если хотите отправить ответ клиенту. Если возникла ошибка, вызовите callback (someErrorVariable), а в случае успеха вызовите callback (null, dataToReturn).

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

// Create  
context.service.create('myServiceName', urlParameters, body, configs, callback);  
// Read  
context.service.read('myServiceName', urlParameters, configs, callback);  
// Update  
context.service.update('myServiceName', urlParameters, body, configs, callback);  
// Delete  
context.service.delete('myServiceName', urlParameters, configs, callback);

Заключение

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