- TodoMVC
- CommonJS подключеие
- Как это работает
- Серверный рендеринг
- События
- Наследование
- API ns.ViewReact
- API ns.ViewReactCollection
- API ns.BoxReact
- API ReactComponent
- Особенности
Подключение 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');Наследуются методы родительского вида, а для компонента — методы компонента родительского вида и миксины, которые были определены у родительского компонента.
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.
Статичный метод ns.ViewReact, позволяющий расширить описанный при декларации view компонент базовым миксином, обеспечивающим отрисовку компонента по описанным выше правилам.
Статичный метод ns.ViewReact. Создаёт React компонент по его декларации, который потом будет использоваться для рендринга.
Позволяет получить дочернее ns.ViewReact по указанному id (в случае ns.ViewCollection по указанной модели). Используется в методе createChildren связанного с view компонента, что позволяет при наследовании при необходимости переопределить поведение.
Проходит по всем доступным для работы дочерним view для ns.ViewReact. В случае бокса - это активные вью, в случае коллекции - это активные элементы коллекции. Данный метод служит точкой переопределения перебора дочерних элементов в createChildren методе компонента.
Создаёт React элемент c указанием view и models в props. В качестве ключа использует ns.ViewReact~__uniequeId. Также позволяет передать дополнительный props для создаваемого компонента.
Тип React компонента.
none(по умолчанию) - компонент ещё не созданroot- корневой (родительский) компонентchild- дочерний компонентdestroyed- компонент уничтожен
"Тихо" удлаяет React компонент, связанный с ns.ViewReact. Для этого, ns.ViewReact помечается типом, что компонент уничтожен, и уничтожается. Сам же компонент будет удалён при первом же ns.Update.
Используется в 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
)
}
}
});Поведение ns.BoxRact, его методы и описание в layout полностью соответствует ns.Box. Поэтому каких-то особых правил описания его в lyaout нет.
Каждый компонент, связанный с реактивной вьюшкой, расширяет поведение реакт-компонента с помощью специального миксина.
Возвращает модель по id
Возвращает данный указанной модели по определенному 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')
)
}
}
});Аналог 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 у компонента, то он будет сгенерирован автоматически на основании айдишника вьюшки, приведенный из camelCase к минус-разделителям.
ns.ViewReact.define('myView', {
component: {
render() {
// my-view
console.log(this.constructor.displayName)
}
}
})Это удобно, если использовать реакт-миксин для генерации БЭМ-классов, который в качестве имени блока берет displayName компонента.
Если displayName определен в декларации явно, то будет использован он.
Реактивные вьюшки могут использовать стейт реакт-компонента. Стейт сохраняется между перерисовками вьюшки.
Простой вызов setState не вызывает перерисовку компонента, связанного с вью. Для того, чтобы это произошло необходимо явно вызвать this.props.view.invalidate(), перед тем как устанавливать новый стейт.
У реакт-компонента, который связан с вью определен shouldComponentUpdate, который разрешит перерисовку в одном из следующих случаев:
- вьюшка невалидная (поменялись версии моделек или был вызван
invalidate) - один из деток невалидный
- у вьюшки еще нет экземпляра компонента
В реактовых боксах разным экземплярам одной и той же вьюшки будет соответствовать один и тот же экземпляр реакт-компонента. Это значит, что:
- создается меньше экземпляров реакт-компонентов
- перерисовки между двумя экземлярами вида происходит в виртуальном доме, именно за счет того, что
renderвозвращает тот же самый экземпляр реакт-компонента