В этот раз я все сделаю правильно!
whoami
- 5+ лет страдаю от JavaScript Fatigue
- Делаю всякие штуки в DataRobot, чаще — фронтенд
- Немного делаю open source
О проекте
- Одностраничное приложение с небольшим бэкендом на Python
- Нет авторизации, использует API DataRobot'а
- Хранит пользовательский ввод в браузерном хранилище
- Анимации, графики, быстрый отзыв
- UX в самом высоком приоритете — и это касается каждой детали
Зачем страдать?
- Эффект Даннинга — Крюгера 😓
- Вера в лучшее
Требования
- Хорошие инструменты — присутствие которых не замечаешь, но работать становится проще
- Я не хочу каждый раз лезть в документацию чтобы прочитать о необходимых параметрах/структурах (привет, Angular)
- Длина цепочек связей между компонентами/слоями должна быть минимальной, для простоты откладки и рефакторинга
- Когнитивная нагрузка должна быть минимальна при взаимодействии с любыми конструкциями
JSX
Универсальный язык разметки компонентов, совместим с разными реализациями при этом не требуя изменений.
Легко читается и пишется, так как представляет собой смесь давно знакомых вещей.
Не прячет ошибки и не дает возможности делать что-то неявно.
{collection.map(item => (
<ItemView key={item.id} item={item} />
))}
<item-view *ngFor="let item of collection" [item]="item">
</item-view>
{{#each item in collection}}
{{item-view item=item}}
{{/each}}
Статическая типизация
bro
Типизация
- Помогает избежать глупых ошибок и бъет по рукам за глупые костыли
- Спустя время, служит документацией о контрактах в проекте
- Уменьшает количество боли от использования dict'ов в качестве аргументов
Использовал исключительно для уровня доступа к данным и бизнес логике
Flow
- Ужасная конфигурация и отсутствие её документации 😨
- Некоторые фичи могут просто не работать 😱
- Пришлось хранить описание типов для сторонних библиотек прямо в проекте 😡
Immutable JS
- Вся эффективность неизменяемых структур данных
- Аутентичное API
- Очевидные организация и результаты
Легкие операции в Immutable JS
case MessageActionTypes.MESSAGE_CREATED:
return threads.mergeIn([action.message.threadId], {
lastMessage: action.message.text,
timestamp: action.message.timestamp,
});
type ResourceModel = { id: string, ... };
const defaults: ResourceModel = { id: '', ... };
class Resource extends Record(defaults) { ... }
class Resource extends Record(defaults) {
constructor(data: ResourceModel) { super(data); }
}
const resource = new Resource({ id: shortid.generate(),
... });
class Resources {
async create(model)
async retrieve(criterias)
async update(id, model)
async destroy(id)
}
Data Access Object
- Скрывает детали реализации хранилища
- Общий интерфейс для всех коллекций
- Только асинхронные операции
- Только иммутабельные данные
Flux — что хорошего
- Я просто привык к такой организации потока данных
- Максимальное разделение операций с данными и их представлением
- Очень легко рефакторить и мокать
Flux + типизация
class PredResponse extends Action<{ datarow: DataRow,
results: Prediction }> {}
Flux — что плохого
- Только синхронная загрузка
- Исключительно для «кеширования»
export default class Actor {
constructor(dispatcher) {
this.dispatcher = dispatcher;
this.dispatchToken = this.dispatcher.register((message) => {
this.receive(message);
});
}
disable() {
this.dispatcher.unregister(this.dispatchToken);
}
}
class PredictionQueue extends Actor {
constructor(dispatcher) {
super(dispatcher);
this.predictions = new Predictions();
}
async receive(message) {
switch (message.constructor) {
case SyncDataRow:
}
}
}
Transmit
- Нет возможности обрабатывать ошибки или показывать статус загрузки
- Сложно обновлять состояние компонента, если нет нужного промиса (не к чему привязать вызов
forceUpdate()
)
- Плохо интегрируется с Flux
LocalForage
- Автоматически выбирает доступное хранилище: IndexedDB, Local Storage
- Асинхронный интерфейс
- Предлагает доступ и версионирование отдельных инстансов
LocalForage
import Storage from 'localforage';
export default Storage.createInstance({
name: 'prediction-optimization-ux/reasons',
version: 1,
});
await Reasons.setItem(id, reason);
PostCSS + CSS Modules
bro
CSS Modules
- Семантические имена все еще важны
- Храню стили рядом с компонентами
/PredictionInsight.react.js
/PredictionInsight.style.css
React + D3 + React Motion ❤️
❤️
- Исключительно декларативная модель
- WYSIWYG😏
- Намного проще для понимания, в сравнении с кишками селекторов D3
Docker
- Простой способ собрать необходимое окружение и воссоздать его когда угодно
- Compose позволяет описать окружение для работы с несколькими компонентами и запускать всю систему единой командой
- Упрощает деплой компонентов
- Требовательный к ресурсам 😓
Python tool set
- aiohttp — библиотека для работы с HTTP (клиент/сервер)
- trafaret — валидация структур данных
- injections — внедрение зависимостей
- motor — асинхронный драйвер для MongoDB
Data Access Object — доступ к данным
class Resources():
async def create(model)
async def retrieve(criterias)
async def update(id, model)
async def destroy(id)
Data Access Object — обработка запросов
class ResourcesView(View):
async def post()
async def get()
async def patch()
async def delete()
Async/await
- Простая модель
- Возможность использовать стандартные языковые структуры
- Меньше шума в коде