noscript-react
v1.1.4
Published
Расширение noscript, позволяющее использовать React компоненты в качестве отображения
Downloads
15
Readme
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
возвращает тот же самый экземпляр реакт-компонента