isomorphic react.js (by maksym klymyshyn) - hack'n'tell javascript - 2015.05.16
TRANSCRIPT
Проблемы one page apps
Зачем это надо?
‣ Производительность - загрузка данных, задержка отображения при старте
‣ Тяжелая операция по рендерингу и созданию DOM-дерева
‣ Недружелюбные для краулеров (hashbang)
Проблемы архитектуры
Зачем это надо?
‣ Двойная валидация входных данных
‣ Поддержка сложной бизнес-логики одновременно на клиенте и на сервере
‣ Зависимость от сервера в мобильных приложениях
‣ Параллельные процессы обмениваются информацией
‣ Безбуферный обмен информацией типа «рандеву»
‣ Взаимодействие происходит через канал
CSP channels
var ch = chan();
go(function*() { yield put(ch, 1); console.log(2); yield put(ch, 3); console.log(yield take(ch)); });
go(function*() { console.log(yield take(ch)); //yield timeout(100); console.log(4); console.log(yield take(ch)); yield put(ch, 5); });
Пример
1 4 2 3 5
Результат
Что нужно
‣ Рендерить компоненты на сервере
‣ Обновлять данные на клиенте без перезагрузки страницы
‣ Максимально унифицировать код сервера и клиента
Состояние запроса
‣ Location
‣ Cookies
‣ GET params
‣ POST params
request = url + cookies + get + post
Пример{ client: (location) => { return state.merge(Map([ ["url", location.pathname], ["protocol", location.protocol], ["hostname", location.hostname], ["hash", location.hash] ])); }, server: (request) => { return state.merge(Map([ ["url", request.url], ["method", request.method] ])); } }
Архитектурно
state = f(request) dom_cli = React.render(…state) html_srv = React.renderToString(…state)
Примерmodule.exports = React.createClass({ statics: { // state will be executed within CSP `go` routine state: function(state, channel) { return go(function * () { yield put(channel, ["title", "World"]); channel.close(); }) } }, render: function () { return <h1>Hello, <span>{this.props.title}</span></h1> }})
Примерvar stateCh = chan(), component = React.createFactory(type); type.state ? type.state(state, stateCh) : null; go(function *() { var context = Map(yield take(stateCh)); yield put(renderCh, Map([ … [keys.state, context], [keys.render, {server: server(component), client: client(component)}] ]))});
Примерvar client = (component) => { return (context) => { return React.render( component(context.get(keys.state).toObject()), document.getElementById(context.get(keys.name))); }}; var server = (component) => { return (context) => { return React.renderToString( component(context.get(keys.state).toObject())); }};
Как это сделать в React.js
‣ в root-овом компоненте определить статический изоморфный метод, который
‣ собирает состояние запроса: path, cookies get params, post params
‣ консолидирует забор данных
‣ отложить рендер root-ового компонента до конца выполнения метода
Решения
‣ Relay – анализ запросов, cache engine
‣ GraphQL – консолидирует сложность API
‣ Transmit – очень тупая query engine, которая делегируется на код query
Relay
Relay.createContainer(Story, { queries: { story: graphql` Story { author { name, profile_picture { uri } }, text}` }
Transmit.createContainer(Main, { queryParams: { pagesToFetch: 10 }, queries: { /** * Return a Promise */ data: function (queryParams) { // isomorphic fetch return fetch(…).then(…) } }
Transmitзамена relay+graphql на промисы
Transmit
Попробовать можно в google://react-isomorphic-starterkit
Service
‣ Асинхронная загрузка данных располагает к медленному бекенду
‣ Тупое кеширование приводит к сложной инвалидации
Проблемс
Выводы
‣ Увеличивается количество shared кода, уменьшается рассеивание бизнес логики между разными платформами (клиент, сервер, мобильные)
‣ Улучшается UX – за счет пререндеринга пользователь получает картинку на экране быстрее
‣ Улучшается видимость в поисковых системах
‣ Не нужно все переписывать на JavaScript
Недостатки
‣ Ограничения всех платформ, учавствующих в выполнении приложения (клиент ∩ сервер ∩ мобильный)
‣ Увеличивает количество компонентов в системе (если не node.js-based проект)
‣ Сложнее тестировать