Skip to content

Instantly share code, notes, and snippets.

@Tauka
Last active November 22, 2019 09:59
Show Gist options
  • Save Tauka/bb964ceccd73b5e06092add7f1e82b89 to your computer and use it in GitHub Desktop.
Save Tauka/bb964ceccd73b5e06092add7f1e82b89 to your computer and use it in GitHub Desktop.
Слабосвязные фичи в effector

Слабосвязные фичи в effector

Мотивация

Слабая связность и высокая переиспользуемость представлений + логики

Достоинства

  • Высокая переиспользуемость БЛ
    Несколько безсвязных фич, могут использовать внутри себя другую фичу со всеми ее вытекающими. Композиция фич на уровне effector
  • Абстракция БЛ
    Не нужно знать насколько сложная в нижней фиче логика, если работает один раз, будет работать всегда, главное передавать нужные данные
  • Дробление на уровне effector
    Таким образом можно разбивать большие фичи на маленькие переиспользуемые, и лучше разделять зоны отвественности

Проблемы

  • Легко выстрелить себе в ногу устаревшим значением
    Если форвардить значение с нескольких сторов в другой стор, и при этом каким-то образом один из сторов получает undefined в хэндлере, то стор в который форвардят будет хранить старое значение

  • Необходимость в активации роута
    При переходе на роут, сторы верхнего уровня (хост) должны будут вызвать апдейт, чтобы данные дошли нижнего уровня

  • Неочевидные связи
    Когда несколько фич связываются таким образом, сложность связей возрастает

Пример

Пример

В этом примере есть 3 фичи, dollar, euro, calculator. Калькулятор является универсальным, т.е. его можно использовать для подсчетов как долларов, так и евро.

// calculator/model.js

import { createStore, combine } from "effector";

export const $exRate = createStore(0);
export const $input = createStore(0);

export const $kzt = combine($input, $exRate, (input, exRate) => {
  return parseInt(input, 10) * exRate;
});

export const $calcUI = combine({
  exRate: $exRate,
  input: $input,
  kzt: $kzt
});

Калькулятор зависит только от своих сторов, все что она делает это рендерит свои сторы и не знает ничего о том что снаружи

import React from "react";
import { useStore } from "effector-react";

import { $calcUI } from "./model";

const Calculator = () => {
  const { exRate, kzt } = useStore($calcUI);

  return (
    <div>
      <div> Exchange rate: {exRate} </div>
      <div> KZT: {kzt} </div>
    </div>
  );
};

export default Calculator;

Теперь мы хотим переиспользовать эту логику в контексте долларов и евро, для этого все что нужно сделать, это передать (forward) нужные значения в калькулятор

// euro/model.js

import { createStore, createEvent } from "effector";
import { $exRate, $input } from "../calculator/model";
import { routeForward } from "../../utils";

export const $euroInput = createStore("0");
const $euroRate = createStore(436);
export const evChangeInput = createEvent();

$euroInput.on(evChangeInput, (_, val) => val);

routeForward({
  from: $euroInput,
  to: $input,
  route: "euro"
});

routeForward({
  from: $euroRate,
  to: $exRate,
  route: "euro"
});

Мы заставили калькулятор считать евро!

import React from "react";
import { useStore } from "effector-react";

import Calculator from "../calculator/Calculator";
import { evGoTo } from "../../router";
import { $euroInput, evChangeInput } from "./model";

const Euro = () => {
  const input = useStore($euroInput);

  return (
    <div>
      <h1> Euro to KZT </h1>
      <input onChange={ev => evChangeInput(ev.target.value)} value={input} />
      <Calculator />
      <button onClick={() => evGoTo("dollar")}> Dollar </button>
    </div>
  );
};

export default Euro;

Примерно тоже самое у нас и для долларов

Обратите внимание на routeForward, это forward с активацией на определенном роуте, то есть при переходе на /dollar передаются актульные значения со сторов:

export const routeForward = ({ from, to, route }) => {
  const routeFrom = sample(from, evGoTo.filter({ fn: path => path === route }));
  
  // forward from value when you go to path
  forward({
    from: routeFrom,
    to
  });

  // forward from when from itself is updated
  forward({
    from: from,
    to
  });
};

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

Второй forward, обычный, передает значение сторов при их обновлении

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment