@kamyshev_code
Авиасейлс
@kamyshev_code
@kamyshev_code
@kamyshev_code
@kamyshev_code
@kamyshev_code
@kamyshev_code
@kamyshev_code
При рендеринге страницы на сервере, фронтенд-приложение не существовало. Вообще никакого контекста не существовало.
То есть рисовать разный UI в зависимости от пользовательского ввода было нельзя.
@kamyshev_code
Нет контроля у приложения за HTML, который приходит с сервера. Приходится полностью рендерить интерфейс на клиенте ещё раз.
@kamyshev_code
Любой тест с SSR — очереди и не очень удобные деплои.
@kamyshev_code
@kamyshev_code
@kamyshev_code
@kamyshev_code
@kamyshev_code
Селена — небольшой сервис, который умеет рендерить React-приложения в HTML-строки.
@kamyshev_code
При рендеринге страницы на сервере, фронтенд-приложение имеет весь нужный контекст.
Мы можем показывать разным пользователям разный интерфейс — по геолокации, группе в AB-тесте или знаку зодиака.
@kamyshev_code
Приложение точно знает какой будет HTML. Можно не перерендеривать все, а только навесить обработчики событий.
@kamyshev_code
Любой тест с SSR можно проводить на Флагре без очередей и СМС.
@kamyshev_code
@kamyshev_code
@kamyshev_code
@kamyshev_code
@kamyshev_code
@kamyshev_code
Мы сделали микрофронтенды. И огребли все проблемы микрофронтендов.
В первую очередь, общение разных приложений на клиенте.
@kamyshev_code
@kamyshev_code
@kamyshev_code
const bus = new CustomEventBus({ /* ... */ });
const originChanged = bus
.take(AVIAFORM_CHANGED)
.map(({ data }) => data.origin);
// ... 🤔
const bus$ = /* ... */;
const origin$ = bus$.pipe(
filter(({ type }) => type === AVIAFORM_CHANGED),
map(({ data }) => data.origin),
);
Поняли, что написали свой Rx и выкинули его, заменив на настоящий Rx.
@kamyshev_code
И тут началось
@kamyshev_code
@kamyshev_code
(нет)
@kamyshev_code
@kamyshev_code
@kamyshev_code
@kamyshev_code
✅ Ничего не нужно менять
⛔️ Нужно продолжать страдать
@kamyshev_code
✅ Знакомый старый друг
⛔️ Очень заточен на единственный стор
⛔️ Переиспользовать кусочки логики между изолированными приложениями больно
@kamyshev_code
✅ Мультисторы — кайф
✅ Супер-удобный интероп с RxJS
⛔️ Работа с SSR
⛔️ Все еще тяжеловат (60+ кб)
⛔️ Фанатично императивный, бывает сложно следить за потоком данных.
@kamyshev_code
✅ Типизация
✅ Нет проблем с SSR + дата-фетчингом
✅ Наш, русский!
⛔️ Чтобы прочитать сорцы нужно иметь степень по компьютер-сайнс
⛔️ Экосистема не слишком богата
⛔️ Фанатично декларативный, бывает сложно писать
@kamyshev_code
@kamyshev_code
// новое реактивное значение
const $value = createStore('')
// новое событи
const smthHappened = createEvent()
// новый сайд-эффект
const writeToLocalStoreFx = createEffect(({ key, vale }) => localStorage.set(key, value))
// создание связей
sample({
source: $value,
clock: smthHappened,
fn: (value) => ({ key: 'SOME_KEY', value }),
target: writeToLocalStoreFx,
})
@kamyshev_code
const $login = createStore('')
const $password = createStore('')
const loginFormSubmit = createEvent()
const $userName = createStore(null)
const loginFx = createEffect(async ({ login, password }) => {...})
sample({
source: {
login: $login,
password: $password,
},
clock: loginFormSubmit,
target: loginFx,
})
sample({
clock: loginFx.doneData,
fn: (user) => user.name,
target: $userName,
})
@kamyshev_code
@kamyshev_code
без страданий
@kamyshev_code
@kamyshev_code
// https://share.effector.dev/SEQBqetV
const $counter = createStore(0)
const increase = createEvent()
$counter.on(increase, count => count + 1)
const scope = fork()
console.log('ORIGINAL', $counter.getState()) // 0
console.log('SCOPE', scope.getState($counter)) // 0
await allSettled(increase, { scope })
console.log('ORIGINAL', $counter.getState()) // 0
console.log('SCOPE', scope.getState($counter)) // 1
@kamyshev_code
который решает проблемы
@kamyshev_code
// https://share.effector.dev/3HsxG6u7
const $counter = createStore(0)
const asyncIncreaseFx = createEffect(() => wait(1000))
const complexFx = createEffect(() => {
// DO NOT AWAIT!!!
asyncIncreaseFx()
})
$counter.on(asyncIncreaseFx.done, count => count + 1)
const scope = fork()
await allSettled(complexFx, { scope })
console.log(scope.getState($counter)) // 1
@kamyshev_code
@kamyshev_code
// https://share.effector.dev/7euTAEJv
const $counter = createStore(0)
const asyncIncreaseFx = createEffect(async () => {
console.log('REAL')
await wait(1000)
})
$counter.on(asyncIncreaseFx.done, count => count + 1)
const scope = fork({
handlers: [
[asyncIncreaseFx, () => console.log('FAKE')]
]
})
await allSettled(asyncIncreaseFx, {scope}) // FAKE
@kamyshev_code
// https://share.effector.dev/WhINUSBd
const $counter = createStore(1000)
const scope = fork({
values: [
[$counter, 1]
]
})
console.log($counter.getState()) // 1000
console.log(scope.getState($counter)) // 1
@kamyshev_code
@kamyshev_code
describe('analyticsService', () => {
test('sends events for inited service instantly', async () => {
const sendMock = jest.fn();
const scope = fork({
// Пусть аналитика уже инициализирована
values: [[$inited, true]],
// Подменим эффект отправки событий
handlers: [[sendAnalyticsEventFx, sendMock]],
});
await allSettled(sendEvent, { scope, params: FAKE_EVENT_1 });
expect(sendMock).toHaveBeenCalledTimes(1);
expect(sendMock).toBeCalledWith(null, FAKE_EVENT_1);
await allSettled(sendEvent, { scope, params: FAKE_EVENT_2 });
expect(sendMock).toHaveBeenCalledTimes(2);
expect(sendMock).toBeCalledWith(null, FAKE_EVENT_2);
});
})
@kamyshev_code
@kamyshev_code
async function renderApp({ query, lang, cookie }) {
const scope = fork(root, {
values: [
[$query, query],
[$language, lang],
],
handlers: new Map([
[readCookieFx, (key) => cookie[key] ?? null],
]),
});
await allSettled(appInited, { scope });
const element = createElement(
'div',
{ 'data-scope': JSON.stringify(serialize(scope)) },
createElement(
Provider,
{ value: scope },
createElement(App);
)
)
return renderToString(element);
}
это был наш путь ☄️
github.com/sponsors/effector
twitter.com/EffectorJS
t.me/kamyshev_code
twitter.com/kamyshev_code
blog.kamyshev.me/tag/effector/