Паттерн подписчик в mobx

В прошлый раз я писал про createAtomSubscriber и рассказывал про его чрезвычайную полезность. Предлагаю написать примитив Subscriber для реактивной подписки/отписки/переподписки на источники данных. Начнем с примера использования подобной конструкции. Представим, что у нас есть функция receiveData, которая генерирует данные, после передачи ей текстового id и реактивная переменную $id. С помощью Subscriber создадим реактивную подписку. Когда getId будет возвращать значение отличное от undefined и subscriber.data будет под наблюдением - у нас запустится receiveData и, с помощью push, мы будет сохранять последнее полученное значение. Когда значение, возвращаемое getId, изменится (важно чтобы оно было реактивным), произойдет отписка. Если новое значение будет не undefined, то произойдет переподписка. Когда subscriber.data выходит из под наблюдения всегда происходит отписка, вне зависимости от getId. ...

May 22, 2023 · 2 min · 409 words · lexich

Работа с atom в mobx

В библиотеке mobx есть очень интересный примитив атом, который создается через createAtom. С его помощью можно создавать очень интересные конструкции и вообще построить чуть ли не всю архитектуру приложения. Что нам говорит документация: `createAtom` {🚀} Usage: `createAtom(name, onBecomeObserved?, onBecomeUnobserved?)` Creates your own observable data structure and hooks it up to MobX. Used internally by all observable data types. Atom exposes two `report` methods to notify MobX with when: - `reportObserved()`: the atom has become observed, and should be considered part of the dependency tree of the current derivation. - `reportChanged()`: the atom has changed, and all derivations depending on it should be invalidated. А давайте немного перезавернем его, чтобы в дальнейшем удобнее было им пользоваться. ...

May 18, 2023 · 2 min · 392 words · lexich

Асинхронная пагинация

Сегодня хочется немного рассказать про собственную наработку https://github.com/lexich/async-paginator Это небольшая библиотека, которая позволяет обрабатывать коллекции данных, используя асинхронные итераторы. При этом доступна гибкая настройка количества одновременного запуска задач (можно запустить сразу все или разбить на группы). import { paginatorUnordered, paginator } from 'async-paginator'; const sleep = (delay: number) => new Promise<void>((resolve) => setTimeout(resolve, delay)); // for ordering use `paginator` instead of paginatorUnordered const paginate = paginatorUnordered([1, 2, 3, 4, 5, 6, 7, 8], async (num) => { if (num % 2 === 0) { await sleep(10); // timeout 10ms } return num * 10; }, { offset: 1, chunks: 2, mode: 'chunks', }); const result: number[] = []; for await (const item of paginate) { if (!(item instanceof PaginationAsyncError)) { result.push(item.data); } } console.log("RESULT: ", result); // RESULT: [30, 20, 50, 40, 70, 60, 80]

May 17, 2023 · 1 min · 135 words · lexich

Брендированные типы в typescript

Cегодня хочется поговорить о брендированных типах в typescript. Для начала, представим, что у нас есть функция для конвертации валют. const usdToEur = (usdAmount: number, rate = 0.92) => usdAmount * rate; usdToEur(100); // === 92 eur Давайте немного улучшим ее, введя типы USD и EUR, чтобы не только название функции и коментарии к ней подсказывали нам о ее предназначении. type USD = number; type EUR = number; const usdToEur = (usdAmount: USD, rate = 0.92): EUR => usdAmount * rate; usdToEur(100); // === 92 eur Стало гораздо лучше, но фактически ничего не изменилось, тк мы можем также передавать любое число и нововведенные типы не защищают нас от ошибки. И здесь нам на помощь приходят брендированные типы. Смысл в том, что мы подмешиваем в описание типа дополнительные поля, которые по факту (в рантайме) не существуют, но при этом typescript осуществляет по ним проверку. ...

May 15, 2023 · 2 min · 225 words · lexich

Миграция в React c Context API в Mobx часть 2

В продолжение вчерашней темы, про миграцию на mobx резонно возникает вопрос, а зачем вообще мигрировать на что-то, если есть redux, context api, useReducer и всякие подобные решения на которых строится сейчас React разработка. export const AppContext = createContext({}); export const AppContextProvider: FC = ({ children }) => { const [user, setUser] = useState<IUser>(undefined); const [profile, setProfile] = useState<IProfile>(undefined); const setUserId = (id: string) => loadUser(id).then(setUser); useEffect(() => { if (user) { loadProfile(user).then(setProfile); } else { setProfile(undefined); } }, [user]); return ( <AppContext.Provider values={{ user, profile, setUserId }}> {children} </AppContext.Provider>); } const Component: FC = () => { const { profile } = useContext(AppContext); return <div>{profile.content}</div> } Представим виртуальный пример, который достаточно просто встретить во многих проектах. Что в нем не так? Проведем мысленный эксперимент. У нас есть компонент, который отображает profile. Сколько раз перерендерится наш компонент, когда мы вызовем setUserId. Первый ре-рендер будет на setUser, потом у нас вызовется useEffect. Его бы я вообще примерно никогда не использовал для работы со стейтом. Далее setProfile и это уже второй ререндер. Такое же поведение будет в каждом компоненте, использующим этот контекст. И неоптимальность - это полбеды, но если, например, мы хотим небольшой тест, чтобы проверить, как это все работает… я даже думать об этом не хочу и перехожу в лагерь тех кто не пишет тесты и рассуждают о них только на собеседовании. ...

May 12, 2023 · 2 min · 383 words · lexich