noscript-react v1.1.4
React + Noscript
Оглавление
- TodoMVC
- CommonJS подключеие
- Как это работает
- Серверный рендеринг
- События
- Наследование
- API ns.ViewReact
- API ns.ViewReactCollection
- API ns.BoxReact
- API ReactComponent
- Особенности
CommonJS подключеие
Подключение npm пакета noscript-react в CommonJS стиле производится следующим образом:
var NSReact = require('noscript-react');
var NS = require('noscript');
var ns = NS();
// Наложение расширения на noscript
NSReact(ns);В этом случае React и ReactDOM будут подключены через require в пакете noscript-react.
Как это работает
Есть специальные классы ns.ViewReact, ns.ViewReactCollection и внутренний класс ns.BoxReact. Кроме того, что они имеют все те же поля, что и обычные ns.View, ns.ViewCollection и ns.Box, есть еще поле component — декларация реакт-компонента.
Например,
ns.ViewReact.define('aside', {
component: {
render: function() {
return React.createElement(
'div',
{ className: 'aside' },
// YATE: apply /.views.menu ns-view
this.createChildren('menu')
);
}
}
});По умолчанию, если это поле не указано или не указан метод render в нём, то отрисовывается ReactElement, реализующий тег div и внутренние вью размещаются в нём. Таким образом, сохраняется аналогичное с YATE поведение по формированию отображения ns.View. Стоит отметить, что указанному div добавляются className и data-key из props, которые может получить вьюшка в результате вызова createChildren с пропсами.
Реакт-компоненты в props получают свою вьюшку view и объект с её моделями - models.
С помощью ссылки на view в пропсах есть знания о кусочке дерева, который лежит ниже этой вьюшки, соответственно есть возможность расставить детей в шаблоне.
Обновляются компоненты по привычной ns-схеме: если реактивная вьюшка стала не валидной (поменялись данные, например), то при следующем ns.Update она будет перерисована. Перерисовка происходит средствами React.
Чтобы это реализовать пока пришлось переопределить приватный _updateHTML и _addView у ns.ViewReact и ns.ViewReactCollection. Рассчитываем на то, что в ns эти методы станут публичным, чтобы можно было законно переопределять.
Есть набор ограничений, которым стоит следовать, когда используются реактивные вьюшки и боксы:
- корневая вьюшка
appдолжна быть обязательно ноускриптовой; - реактивный бокс создаётся только когда он был описан как дочерний элемент реактивной вьюшки. В этом случае обычный бокс создан не будет. Поэтому стоит озаботится о подключении
ns.BoxReactк приложению.
Сама реализация ns.ViewReact, ns.ViewReactCollection, ns.BoxReact может находиться в отдельном репо и подключаться к ns в виде плагина, по аналогии с босфорусом.
Серверный рендеринг
Для использования "реактивных" вью на сервере необходимо подключить плагин noscript-bosphorus к приложению и установить глобальных флаг ns.SERVER = true.
Это позволит, используя ns.Update и метод ns.Update.prototype.generateHTML, сгенерировать на сервере HTML страницы, включая в него "реактивные" вью.
Например,
ns.SERVER = true;
ns.layout.define('index', {
app: {
reactView: true
}
});
ns.View.define('app');
ns.ViewReact.define('reactView');
var appView = ns.View.create('app');
var appLayout = ns.layout.page('index');
var update = new ns.Update(appView, appLayout, {});
update.generateHTML()
.then(function(appHTML) {
// Тут доступен HTML приложения в appHTML
});События
Встроенные
Для реактивной вьюшки работают встроенные события.
ns.ViewReact.define('foo', {
events: {
'ns-view-init': function() {
// доопределяем инициализацию
},
'ns-view-htmlinit': function() {
// компонент инициализирован (componentWillMount)
},
'ns-view-show': function() {
// компонент в DOM и виден (componentDidMount)
},
'ns-view-hide': function() {
// компонент cпрячется (меняется лейаут)
},
'ns-view-htmldestroy': function() {
// компонент обновится
}
}
})Порядок всплытия событий сохраняется. ns.Update.prototype.perf учитывает отрисовки и обычных и реактивных видов.
«Космические»
Работают «космические» события по аналогии с обычными вью.
ns.ViewReact.define('foo', {
events: {
'my-global-event@show': function() {},
'my-global-event@init': function() {}
}
})Наследование
Как и для обычного вида, для реактивного, можно указывать базовый вид.
ns.ViewReact.define('bar', {
methods: {
helloFromViewBar: function() {}
},
component: function() {
helloFromComponentBar: function() {}
}
});
ns.ViewReact.define('foo', {
events: {
'ns-view-htmlinit': function() {
// унаследовали метод родительской вьюшки
this.helloFromViewBar();
}
},
component: {
hello: function() {
// унаследовали метод родительского компонента
this.helloFromComponentBar();
}
}
}, 'bar');Наследуются методы родительского вида, а для компонента — методы компонента родительского вида и миксины, которые были определены у родительского компонента.
API ns.ViewReact
ns.ViewReact - это наследник ns.View, который вместо YATE использует ReactComponent.
Выделяется 3 типа связанных компонентов с ns.ViewReact:
none- компонент ещё не создавался (отсутствует).root- корневой компонент. С него начинается создание вложенных вns.ViewReactкомпонентов (другихns.ViewReact).child- дочерний компонент. Это компонент, который размещён в какому-тоrootна любом уровне вложенности.destroyed- компонент уничтожен в момент уничтоженияns.ViewReact.
Такое деление было введено для того, чтобы понимать, когда необходимо вызвать ReactDOM.render, а когда forceUpdate для ReactComponent.
Каждый раз, когда _updateHTML вызывается у ns.ViewReact, происходит актуализация состояния вложенных в неё вью. Это позволяет выяснить, какая часть дерева стала невалидной и перерисовать её. При первом вызове - невалидно всё дерево.
Перерисовка чаще всего вызывается на root компоненте. Но возможен вызов и на child компоненте. Например, если ns.ViewReact, содержащая child компонент, является асинхронной или обновление было вызвано через метод ns.ViewReact~update.
#mixComponent
Статичный метод ns.ViewReact, позволяющий расширить описанный при декларации view компонент базовым миксином, обеспечивающим отрисовку компонента по описанным выше правилам.
#createClass
Статичный метод ns.ViewReact. Создаёт React компонент по его декларации, который потом будет использоваться для рендринга.
#getChildView
Позволяет получить дочернее ns.ViewReact по указанному id (в случае ns.ViewCollection по указанной модели). Используется в методе createChildren связанного с view компонента, что позволяет при наследовании при необходимости переопределить поведение.
#forEachItem
Проходит по всем доступным для работы дочерним view для ns.ViewReact. В случае бокса - это активные вью, в случае коллекции - это активные элементы коллекции. Данный метод служит точкой переопределения перебора дочерних элементов в createChildren методе компонента.
#createElement
Создаёт React элемент c указанием view и models в props. В качестве ключа использует ns.ViewReact~__uniequeId. Также позволяет передать дополнительный props для создаваемого компонента.
#reactComponentType
Тип React компонента.
none(по умолчанию) - компонент ещё не созданroot- корневой (родительский) компонентchild- дочерний компонентdestroyed- компонент уничтожен
#softDestroy
"Тихо" удлаяет React компонент, связанный с ns.ViewReact. Для этого, ns.ViewReact помечается типом, что компонент уничтожен, и уничтожается. Сам же компонент будет удалён при первом же ns.Update.
Используется в ns.ViewReactCollection.
API ns.ViewReactCollection
Коллекция наследуется от ns.ViewReact, поэтому имеет схожее с ним API. Определение коллекции производится аналогично ns.ViewCollection. Отличием является то, что элементы ns.ViewReactCollection - это реактивные вью ns.ViewReact. Поэтому они должны быть определены через ns.ViewReact.define.
Пример создания коллекции:
ns.Model.define('list', {
split: {
items: '/',
params: {
'id': '.id'
},
model_id: 'item'
},
methods: {
request: function() {
return Vow.fulfill([
{id: 1, value: 1},
{id: 2, value: 2},
{id: 3, value: 3}
]).then(function(data) {
this.setData(data);
}, this);
}
}
});
ns.Model.define('item', {
params: {
id: null
}
});
ns.ViewReactCollection.define('list', {
models: ['list'],
split: {
byModel: 'list',
intoViews: 'item'
},
component: {
render: function() {
return React.createElement(
'div',
{ className: 'list' },
this.createChildren()
)
}
}
});
ns.ViewReact.define('item', {
models: ['item'],
component: {
render: function() {
return React.createElement(
'div',
{ className: 'item' },
this.state.item.value
)
}
}
});API ns.BoxReact
Поведение ns.BoxRact, его методы и описание в layout полностью соответствует ns.Box. Поэтому каких-то особых правил описания его в lyaout нет.
API ReactComponent
Каждый компонент, связанный с реактивной вьюшкой, расширяет поведение реакт-компонента с помощью специального миксина.
getModel
Возвращает модель по id
getModelData
Возвращает данный указанной модели по определенному jpath. Если jpath не указан — вернутся все данные.
ns.ViewReact.define('articleCaption', {
models: ['article'],
component: {
render: function() {
return React.createElement(
'h1',
{ className: 'article-caption' },
// YATE: model('article').caption
this.getModelData('article', '.caption')
)
}
}
});createChildren
Аналог apply /.views.view ns-view или apply /.views.* ns-view в yate.
Создаст реакт-элементы для указанных реактивных вьюшек, если они есть среди активных потомков текущей вьюшки. Если указанной вьюшки нет, вернет null. Позволяет передать props для создаваемых реакт-элементов.
Возможные варианты вызова:
this.createChildren() // создаст компоненты для всех дочерних view
this.createChildren({length: 25}); // создаст компоненты для всех дочерних view и передаст им указанные props
this.createChildren('child-view') // создаст дочернее view с id `child-view`.
this.createChildren('child-view', {length: 25}) // создаст дочернее view с id `child-view` и передаст в неё указанные props
this.createChildren(['child-view1', 'child-view2']); // создаст дочерние view с id `child-view1`, `child-view2`
this.createChildren(['child-view1', 'child-view2'], {length: 25}); // создаст дочерние view с id `child-view1`, `child-view2` и передаст в них указанные propsРазличия:
- Для
ns.ViewReactметод принимаетidвьюшек, которые нужно создать, иpropsдля их компонентов. - Для
ns.ViewReactCollectionметод принимает модели коллекций, с которыми связаны создаваемые вьюшки, иpropsдля компонентов элементов коллекции.
Особенности
Дефолтный displayName
Если не указывать displayName у компонента, то он будет сгенерирован автоматически на основании айдишника вьюшки, приведенный из camelCase к минус-разделителям.
ns.ViewReact.define('myView', {
component: {
render() {
// my-view
console.log(this.constructor.displayName)
}
}
})Это удобно, если использовать реакт-миксин для генерации БЭМ-классов, который в качестве имени блока берет displayName компонента.
Если displayName определен в декларации явно, то будет использован он.
Работа со стейтом
Реактивные вьюшки могут использовать стейт реакт-компонента. Стейт сохраняется между перерисовками вьюшки.
setState не вызывает перерисовку
Простой вызов setState не вызывает перерисовку компонента, связанного с вью. Для того, чтобы это произошло необходимо явно вызвать this.props.view.invalidate(), перед тем как устанавливать новый стейт.
У реакт-компонента, который связан с вью определен shouldComponentUpdate, который разрешит перерисовку в одном из следующих случаев:
- вьюшка невалидная (поменялись версии моделек или был вызван
invalidate) - один из деток невалидный
- у вьюшки еще нет экземпляра компонента
Умные перерисовки в боксах
В реактовых боксах разным экземплярам одной и той же вьюшки будет соответствовать один и тот же экземпляр реакт-компонента. Это значит, что:
- создается меньше экземпляров реакт-компонентов
- перерисовки между двумя экземлярами вида происходит в виртуальном доме, именно за счет того, что
renderвозвращает тот же самый экземпляр реакт-компонента