

Архитекута "Личного Кабинета"
Что изменилось за 2015 год?
Что за проект?
Контент
Статистика
Реклама
Контакты, время работы, вход, скидки, буклеты, фотографии
Рекламная статистика, переходы из разных источников, просмотры, позиция в выдаче.
CPS, MFH, ставки, плательщики, финансовая детализация, заказы, брендирование.
Технологический стэк
Catbee
Изоморфный фреймворк, образующая технология. Fork от Catberry.
Baobab
Структура данных позволяющая хранить состояние приложения и работать с ним распределено.
Signals
Механизм модификации Baobab, упрощающий работу с async/sync операциями
Web Components
Web Components.
<input type="text" />
Год 2003.
Web Components.
<input type="number">
#shadow-root (user-agent)
<div id="text-field-container" pseudo="-webkit-textfield-decoration-container">
<div id="editing-view-port">
<div id="inner-editor"></div>
</div>
</div>
<div pseudo="-webkit-inner-spin-button" id="spin"></div>
</input>
Год 2016.
Web Components.
<input type="date">
#shadow-root (user-agent)
<div pseudo="-webkit-datetime-edit" id="date-time-edit" datetimeformat="dd.MM.yy">
<div pseudo="-webkit-datetime-edit-fields-wrapper">
<span role="spinbutton" aria-valuetext="нет данных" aria-valuemin="1" aria-valuemax="31" aria-help="День" pseudo="-webkit-datetime-edit-day-field">дд</span>
<div pseudo="-webkit-datetime-edit-text">.</div>
<span role="spinbutton" aria-valuetext="нет данных" aria-valuemin="1" aria-valuemax="12" aria-help="Месяц" pseudo="-webkit-datetime-edit-month-field">мм</span>
<div pseudo="-webkit-datetime-edit-text">.</div>
<span role="spinbutton" aria-valuetext="нет данных" aria-valuemin="1" aria-valuemax="275760" aria-help="Год" pseudo="-webkit-datetime-edit-year-field">гггг</span>
</div>
</div>
</input>
Go Harder!!!
Web Components.
<cat-button id="callback-submit-button" theme="blue" label="Заказать звонок">
<button class="_button_6v0cw_1 _blue_1gav3_36">
Заказать звонок
</button>
</cat-button>
Catbee version.
Data
{
isDisabled: ['path', 'to', 'javascript', 'boolean', 'value'],
value: ['path', 'to', 'javascript', 'string'],
anythingElse: ['path', 'to', 'anything', 'else']
}
Данные для компонента (aka Watcher)
1. Формировать контекст для отрисовки
2. Уведомлять компонент о изменении данных
Цели и задачи:

Структура данных
Требования к состоянию приложения
Каждый вложенный объект Event Emitter
Компоненты должны как-то знать о том что данные изменились и нужно перерисовать представление
Не должно быть непредсказумых мутаций
Вся система должна знать о изменениях той или иной части состояния или будет рассинхрон.
Ссылки на объекты должны быть защищены.
+ Нужно мутации состояния инкапсулировать от компонента.

var Baobab = require('baobab');
var tree = new Baobab({
palette: {
colors: ['yellow', 'purple'],
name: 'Glorious colors'
}
});
var colorsCursor = tree.select(['palette', 'colors']);
colorsCursor.on('update', function() {
console.log('Selected colors have updated!');
});
colorsCursor.push('orange');
Signals
aka State Controller
Signals
Базовый пример
exports.basicSignal = [
setPageTitle,
setPageDescription
]
function setPageTitle (args, state) {
state.set(['page', 'title'], 'account.2gis.ru - очень крутой');
}
function setPageDescription (args, state) {
state.set(['page', 'description'], 'Все разработчики ЛК няши! :meow:')
}
Signals
Пример посложнее
exports.advancedSignal = [
setLoading, // Sync function
[ // Here we run parallel functions
getUser, { // Async function with 2 outputs success and error
success: [setUser], // Run if we call output.success in getUser
error: [setUserError] // Run if we call output.error in getUser
},
getNews, { // It's function will run paraller with getUser, like Promise.all
loaded: [setNews],
error: [setNewsError]
}
],
unsetLoading
]
function getUser (args, state, output) {
ajax.get('/url')
.then(function (response) {
output.success({ news: response });
}
.catch(function (response) {
output.error({ error: response.code });
})
}
Signals
Мирко оптимизация
exports.advancedSignal = [
setLoading, // Sync function
[ // Here we run parallel functions
...getUserGroup,
...getNewsGroup
],
unsetLoading
];
var getUserGroup = [
getUser, {
success: [setUser], // Run if we call output.success in getUser
error: [setUserError] // Run if we call output.error in getUser
}
];
var getNewsGroup = [
getNews, { // It's function will run paraller with getUser, like Promise.all
loaded: [setNews],
error: [setNewsError]
}
]
Signals
Мирко оптимизация
exports.advancedSignal = [
set(['isLoading'], true), // Sync function
[ // Here we run parallel functions
...getUserGroup,
...getNewsGroup
],
unset(['isLoading']
];
function set (path, value) {
return function set (args, state) {
state.set(path, value);
}
}
function unset (path) {
return function unset (args, state) {
state.unset(path);
}
}
Общая схема

Компоненты могут отсылать сигналы, но не могут взаимодействовать на друг друга
Дерево ничего не знает о компонентах и может только сообщать компонентам о изменениях той или иной его части
Computed vs View Model
View Model
Вызов набора последовательных функций
для формирования объекта
которым воспользуется представление
Computed
Правила трансформации одних данных в другие.
Computed
Пример
state.set(['counter'], Baobab.monkey(
['todos'],
(todos = []) => {
return todos.filter((todo) => todo.status == 'active').length)
}
);
1) Занимает место в дереве
2) Это правило, вычисление по требованию
3) Вычисляется 1 раз до следующего изменения
Computed
Сложный пример
state.set(['auction', 'UIState', 'isBidsButtonEnabled'], Baobab.monkey(
['permissions', 'adverts', 'bids'],
['auction', 'campaign', 'permissions', 'isAgreementAccepted'],
['auction', 'computed', 'isBidsChanged'],
['auction', 'computed', 'isBidsValid']
(isBidsEnabled, isAgreementEnabled, isBidsChanged, isBidsValid) =>
_.every([isBidsEnabled, isAgreementEnabled, isBidsChanged, isBidsValid])
));
И немного реального кода...
deck
By Kirill Kaysarov
deck
- 1,034