Все посты

Обзор effector

Содержание

  1. dafuq is effector
  2. Примеры кода
  3. Преимущества effector
  4. Недостатки effector
  5. Заключение

dafuq is effector

Если верить документации на гитхабе, то effector - это

эффективный мульти-сторный менеджер состояния, который позволяет управлять данными в сложных приложениях без риска появления раздутого монолитного стейта.

Опустим срач “один стор vs много сторов”. Основной альтернативой эффектору является mobX. Философия у них одинаковая: куча маленьких, легковесных сторов, которые легко разрабатывать и комбинировать. Даже построены оба на реактивном программировании.

Основные принципы эффектора

  • Сторы должны быть легковесными - добавляйте маленький стор на каждый чих.
  • Возможность объединять сторы - храните данные как угодно и собирайте их в рантайме под задачу.
  • Нет спорных концепций - нет декораторов, нет необходимости использовать классы или прокси. Библиотека использует только функции и простые js-объекты.
  • Понятное API - базовые принципы повторно используется везде. Например, если вы знаете, как .watch работает для событий, то вы уже знаете, как .watch работает для сторов.
  • Построение приложения из простых - упрощаем компоненты, убирая бизнес-логику с глаз долой.

Примеры кода

Заглянем внутрь эффектора. Как будет выглядеть наш новый, модный и молодёжный код?

Базовые понятия

В effector используется два базовых понятия: store и event.

Store - это хранилище данных. Создаётся с помощью функции createStore.

import { createStore } from 'effector';

const counter = createStore(0); // Создание стора с 0 в качестве дефолтного значения
counter.watch(console.log); // Отслеживание изменений в сторе

Философия effector - создавать много таких же легковесных сторов, как в примере выше.

Обновляется стор с помощью подписки на события (events). Для них используется функция createEvent.

Эвенты - аналог actions в Redux, взаимодействие эвента и стора аналогично взаимодействию экшена и редьюсера.

mport { createStore, createEvent } from 'effector';

const increment = createEvent('increment');
const decrement = createEvent('decrement');
const resetCounter = createEvent('reset counter');

const counter = createStore(0)
  .on(increment, state => state + 1) // обработчик эвента, аналог редьюсера. Реагируем на эвент, возвращая новый стор
  .on(decrement, state => state - 1)  
  .reset(resetCounter);

counter.watch(console.log);

Интеграция с React

Используется специальный хук useStore из пакета effector-react.

import ReactDOM from 'react-dom';
import { createStore, createEvent } from 'effector';
import { useStore } from 'effector-react';

const increment = createEvent('increment');
const decrement = createEvent('decrement');
const resetCounter = createEvent('reset counter');

const counter = createStore(0)
  .on(increment, state => state + 1)
  .on(decrement, state => state - 1)
  .reset(resetCounter);

counter.watch(console.log);

const Counter = () => {
  const value = useStore(counter); // подписка на изменения в сторе

  return (
    <>
      <div>Count: {value}</div>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={resetCounter}>reset</button>
    </>
  )
}

const App = () => <Counter />;
const div = document.createElement('div');
document.body.appendChild(div);
ReactDOM.render(
  <App/>,
  div
);

Управление сайд-эффектами

На мой взгляд, одна из самых важных штук. Без вменяемого управления сайд-эффектами любой менеджер состояния - бесполезная погремушка.

После мракобесия в redux многие менеджеры выглядят более, чем адекватно. Эффектор в этом плане мне понравился.

В эффекторе для управления сайд-эффектами используются (барабанная дробь) эффекты! Название явно стащили с RxJS.

Для создания эффекта используется функция createEffect. Не спутайте с useEffect :)

const getUser = createEffect('get user');
getUser.use(params => {
  return fetch(`https://example.com/get-user/${params.id}`)
    .then(res => res.json())
});

// или так

const getUser = createEffect('get user', {
  handler: params => fetch(`https://example.com/get-user/${params.id}`)
    .then(res => res.json())
});

const users = createStore([]) // <-- дефолтный стейт
  // getUser.done - это эвент, который будет запущен при резолве промиса
  .on(getUser.done, (state, {result, params}) => [...state, result]);

Продвинутые штуки

У эффектора есть две прикольные фичи: combine и map.

combine позволяет подписываться на объединение нескольких сторов (привет, reselect)

const balance = createStore(0);
const username = createStore('nikitaulshin');

const greeting = combine(balance, username, (balance, username) => {
  return `Hello, ${username}. Your balance is ${balance}`
});

greeting.watch(data => console.log(data)); // Hello, nikitaulshin. Your balance is 0

map действует наоборот - позволяет подписываться только на часть стора (привет, reselect)

const title = createStore('');
const changed = createEvent();

const length = title.map((title) => title.length);

title.on(changed, (oldTitle, newTitle) => newTitle);

length.watch((length) => console.log("new length is ", length)); // new length is 0

changed("hello"); // new length is 5
changed("world");
changed("hello world"); // new length is 11

Преимущества effector

Объективнее всего будет сравнивать effector с mobX - парадигмы и идеи в целом одни и те же. Итак, чем effector лучше mobX:

  • Чуть легче. MobX весит почти 20кб, Effector - чуть меньше 8кб. Не критично, но приятно.
  • В MobX много магии 🪄 Внутренние механизмы подписки и оповещения работают не всегда очевидно. Не скажу, что effector намного прозрачнее, но всё-таки он более понятен.
  • Иммутабельные сторы. Мутабельность MobX тоже отчасти могу отнести к волшебству 🦄
  • API у эффектора на мой вкус более понятный и очевидный.

Недостатки effector

Главный, на мой взгляд, недостаток эффектора - непонятно, нахрена он вообще нужен. Есть MobX, который решает по сути ту же самую задачу - предоставить приложению множественные сторы. MobX - проверенное решение, которое прижилось в комьюнити и используется многими компаниями в продакшене. Тащить его в боевое приложение ради чуть более красивого API и 10кб бандла - занятие бессмысленное и бестолковое.

В относительных величинах effector неплохо вырос за год. А в абсолютных - практически стоит на месте.

effector npm trends

По сравнению с MobX effector выглядит совсем грустненько.

effector vs mobx

effector - очень маленькая (но очень гордая) библиотека. Да, его довольно активно развивают, но надолго ли хватит энтузиазма авторов и комьюнити?

Отдельно хочу отметить как недостаток то преимущество, которое особенно нахваливают все вокруг - простота. Простой как кирпич инструмент со временем обычно прилетает в голову. Библиотека может быть как угодно хороша на словах, но на деле можно выстрелить себе в ногу. Эффектор - не исключение (как сделать throttling/batching?).

Заключение

effector выглядит прикольно. Стильный, модный, молодёжный, реактивное программирование и всё такое.

Но я так и не смог ответить себе на вопрос - нахрена он нужен, если есть mobX, которые решает те же задачи дольше и стабильнее?

Я бы не стал тащить его в проект ради более красивого API или уменьшения бандла на 12кб. К тому же, нет никакой уверенности о том, что effector на самом деле production-ready. Комьюнити у него ещё маленькое, инфраструктура не развита.

На мой взгляд, эта библиотека не привносит ничего кардинально нового и интересного. Возможно, со временем effector станет более популярен и интересен, но сейчас он ещё сыроват для серьёзных проектов.