Написание адаптера для Fetch API

Кажется, что нет такого проекта, в котором не нужно делать запросы к серверу. И всегда хорошей идеей написать отдельную абстракцию для описания подобной коммуникации. Это добавляет коду не только читаемости и переиспользуемости, но и дает возможность быстро и эффективно написать тест для подобной функциональности. class NetworkService { constructor(private readonly fetch: typeof fetch) {} getUser: (): Promise<IUser> => this.fetch('/api/user').then(d => d.json()) } function getUsername(api: Pick<NetworkService, 'getUser'>) { return api.getUser().then(user => user.name); } test('getUsername', async () => { const name = await getUsername({ getUser: () => Promise.resolve({ name: 'test' } as IUser) }); expect(name).toBe('test'); }); И для среднестатистического проекта такого подхода будет достаточно. Но иногда возникают требования, что нужно переопределить сам fetch. Например, у нас в проекте, мы разрабатываем extension для браузера и хотим делать запросы через serviceWorker. И возникает требования переимплементировать Fetch API. Как мы знаем fetch возвращает примитив Response, для создания которого нужно передать первым аргументом ReadableStream, а вторым характеристики ответа. Таким образом мы можем использовать следующую конструкцию. ...

June 19, 2023 · 2 min · 378 words · lexich

DI в typescript в стиле Java

На работе в проекте столкнулись с достаточно распространенной архитектурной проблемой. Есть набор сервисов, зависящих друг от друга. Первоначально взаимосвязи между сервисами описывались c помощью DI через конструктор. interface Options { service1: Service1; service2: Service2; serviceN: ServiceN; } class ServiceNew { constructor(private options: Options) {} } В большинстве случаев такой подход является достаточно эффективным. Однако порой возникают ситуации, например, когда 2 сервиса зависят друг от друга и приходится инжектить зависимость через функции-геттеры. class Sevice1 { constructor( private getService2(): Service2, private serviceN: ServiceN ) {} } class Sevice2 { constructor( private getService1(): Service1, private serviceN: ServiceN ) {} } В ситуации когда таких сервисов например 20, то хочется уже более единообразного подхода. Например мы можем обратиться к опыту языка Java и сохранить все сервисы в один объект и передать ссылку на него в каждый сервис. А для большей изоляции кода можно на уровне типов ограничить список используемых сервисов. Главное условие при таком подходе, что нельзя обращаться к такому объекту в конструкторе, тк в этот момент еще идет наполнение этого объекта. При этом остается декларативность в описании зависимостей и сохраняется единообразность подхода. Также если в каком-то сервисе мы вдруг забудем про объявлении поля ctx, то type-checker об этом напомнит. ...

May 31, 2023 · 2 min · 348 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

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

В жизни можно бесконечно делать 3 вещи, смотреть как горит огонь, как течет вода и как я пытаюсь агитировать переписать react приложение с Context API на что-то более функциональное, на чем можно построить нормальную архитектуру приложения. Последнее время я все чаще выбираю mobx, поэтому поговорим сегодня про него. Так что же делать, если приложение уже написано, а с чего-то начать надо? Предположим, у нас есть компонент, который обращается к глобальному стейту приложения. ...

May 11, 2023 · 2 min · 357 words · lexich