Расширение noscript, позволяющее использовать React компоненты в качестве отображения
npm install noscript-react


* TodoMVC
* CommonJS подключеие
* Как это работает
* Серверный рендеринг
* События
* Встроенные
* «Космические»
* Наследование
* API ns.ViewReact
* #mixComponent
* #createClass
* #getChildView
* #forEachItem
* #createElement
* #reactComponentType
* #softDestroy
* API ns.ViewReactCollection
* API ns.BoxReact
* API ReactComponent
* getModel
* getModelData
* createChildren
* Особенности
* Дефолтный displayName
* Работа со стейтом
* setState не вызывает перерисовку
Подключение npm пакета noscript-react в CommonJS стиле производится следующим образом:
``js
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 — декларация реакт-компонента.
Например,
`js`
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 страницы, включая в него "реактивные" вью.
Например,
`js
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
});
`
Для реактивной вьюшки работают встроенные события.
`js`
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 учитывает отрисовки и обычных и реактивных видов.
Работают «космические» события по аналогии с обычными вью.
`js`
ns.ViewReact.define('foo', {
events: {
'my-global-event@show': function() {},
'my-global-event@init': function() {}
}
})
Как и для обычного вида, для реактивного, можно указывать базовый вид.
`js`
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.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.Пример создания коллекции:
`js
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
Каждый компонент, связанный с реактивной вьюшкой, расширяет поведение реакт-компонента с помощью специального миксина.
$3
Возвращает модель по id
$3
Возвращает данный указанной модели по определенному jpath. Если jpath не указан — вернутся все данные.
`js
ns.ViewReact.define('articleCaption', {
models: ['article'],
component: {
render: function() {
return React.createElement(
'h1',
{ className: 'article-caption' },
// YATE: model('article').caption
this.getModelData('article', '.caption')
)
}
}
});
`$3
Аналог apply /.views.view ns-view или apply /.views.* ns-view в yate.Создаст реакт-элементы для указанных реактивных вьюшек, если они есть среди активных потомков текущей вьюшки. Если указанной вьюшки нет, вернет null. Позволяет передать
props для создаваемых реакт-элементов.Возможные варианты вызова:
`javascript
this.createChildren() // создаст компоненты для всех дочерних viewthis.createChildren({length: 25}); // создаст компоненты для всех дочерних view и передаст им указанные props
this.createChildren('child-view') // создаст дочернее view с id
child-view.this.createChildren('child-view', {length: 25}) // создаст дочернее view с id
child-view и передаст в неё указанные propsthis.createChildren(['child-view1', 'child-view2']); // создаст дочерние view с id
child-view1, child-view2this.createChildren(['child-view1', 'child-view2'], {length: 25}); // создаст дочерние view с id
child-view1, child-view2 и передаст в них указанные props
`Различия:
1. Для
ns.ViewReact метод принимает id вьюшек, которые нужно создать, и props для их компонентов.
2. Для ns.ViewReactCollection метод принимает модели коллекций, с которыми связаны создаваемые вьюшки, и props для компонентов элементов коллекции.
Особенности
$3
Если не указывать
displayName у компонента, то он будет сгенерирован автоматически на основании айдишника вьюшки, приведенный из camelCase к минус-разделителям.`js
ns.ViewReact.define('myView', {
component: {
render() {
// my-view
console.log(this.constructor.displayName)
}
}
})
`Это удобно, если использовать реакт-миксин для генерации БЭМ-классов, который в качестве имени блока берет
displayName компонента.Если
displayName определен в декларации явно, то будет использован он.$3
Реактивные вьюшки могут использовать стейт реакт-компонента. Стейт сохраняется между перерисовками вьюшки.
$3
Простой вызов
setState не вызывает перерисовку компонента, связанного с вью. Для того, чтобы это произошло необходимо явно вызвать this.props.view.invalidate(), перед тем как устанавливать новый стейт.У реакт-компонента, который связан с вью определен
shouldComponentUpdate, который разрешит перерисовку в одном из следующих случаев:- вьюшка невалидная (поменялись версии моделек или был вызван
invalidate)
- один из деток невалидный
- у вьюшки еще нет экземпляра компонента$3
В реактовых боксах разным экземплярам одной и той же вьюшки будет соответствовать один и тот же экземпляр реакт-компонента. Это значит, что:
- создается меньше экземпляров реакт-компонентов
- перерисовки между двумя экземлярами вида происходит в виртуальном доме, именно за счет того, что
render` возвращает тот же самый экземпляр реакт-компонента