Архитекута "Личного Кабинета"

Что изменилось за 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