Внедрение зависимостей в React
Благодаря наследованию, DI — это широко используемый шаблон в объектно-ориентированном программировании (ООП), предназначенный для многократного использования кода в различных объектах и классах. Однако основной причиной использования внедрения зависимостей в React является простое моделирование и тестирование компонентов. В отличие от Angular, DI не является обязательным требованием при работе с React, это скорее удобный инструмент, который можно использовать, когда вы хотите что-то улучшить.
Внедрение зависимости в JavaScript
Содержание статьи:
- 1 Внедрение зависимости в JavaScript
- 2 Работа с несколькими зависимостями
- 3 Внедрение зависимостей в React
- 4 Внедрение зависимости через props
- 5 Использование TypeScript
- 6 Внедрение зависимости через Context API
- 7 Альтернативы внедрению зависимостей
- 8 Почему вы должны использовать внедрение зависимостей?
- 9 Заключение
Чтобы проиллюстрировать принципы DI, представьте модуль npm, который предоставляет следующую функцию:
JavaScript export const ping = (url) => { return new Promise((res) => { fetch(url) .then(() => res(true)) .catch(() => res(false)) }) }
1234567 | export const ping = (url) => { return new Promise((res) => { fetch(url) .then(() => res(true)) .catch(() => res(false)) })} |
React JS. Основы
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Получить курс сейчас!
Использование функции ping в современном браузере будет работать нормально.
JavaScript import { ping } from «./ping» ping(«https://logrocket.com»).then((status) => { console.log(status ? «site is up» : «site is down») })
12345 | import { ping } from «./ping» ping(«https://logrocket.com»).then((status) => { console.log(status ? «site is up» : «site is down»)}) |
Но запуск этого кода внутри Node.js вызовет ошибку, потому что fetch не реализована в Node.js. Однако существует множество реализаций fetch и полифиллов для Node.js, которые мы можем использовать. DI позволяет нам превратить fetch в инъекционную зависимость для ping, например:
JavaScript export const ping = (url, fetch = window.fetch) => { return new Promise((res) => { fetch(url) .then(() => res(true)) .catch(() => res(false)) }) }
1234567 | export const ping = (url, fetch = window.fetch) => { return new Promise((res) => { fetch(url) .then(() => res(true)) .catch(() => res(false)) })} |
От нас не требуется указывать для fetch значение по умолчанию window.fetch. Каждый раз, когда мы используем ping нам не требуется интегрировать fetch, что улучшает опыт разработки. Теперь в Node среде мы можем использовать fetch в сочетании с нашей функцией ping, например:
JavaScript import fetch from «node-fetch» import { ping } from «./ping» ping(«https://logrocket.com», fetch).then((status) => { console.log(status ? «site is up» : «site is down») })
123456 | import fetch from «node-fetch»import { ping } from «./ping» ping(«https://logrocket.com», fetch).then((status) => { console.log(status ? «site is up» : «site is down»)}) |
Работа с несколькими зависимостями
Если у нас есть несколько зависимостей, невозможно добавлять их как параметры: func (param, dep1, dep2, dep3,…). Вместо этого лучше иметь объект для зависимостей:
JavaScript const ping = (url, deps) => { const { fetch, log } = { fetch: window.fetch, log: console.log, …deps } log(«ping») return new Promise((res) => { fetch(url) .then(() => res(true)) .catch(() => res(false)) }) } ping(«https://logrocket.com», { log(str) { console.log(«logging: » + str) } })
1234567891011121314151617 | const ping = (url, deps) => { const { fetch, log } = { fetch: window.fetch, log: console.log, …deps } log(«ping») return new Promise((res) => { fetch(url) .then(() => res(true)) .catch(() => res(false)) })} ping(«https://logrocket.com», { log(str) { console.log(«logging: » + str) }}) |
Наш параметр deps будет распространен на объект реализации и переопределит функции, которые он предоставляет. При деструктуризации этого измененного объекта уцелевшие свойства будут использоваться в качестве зависимостей. Используя этот шаблон, мы можем переопределить одну зависимость, но не другие.
Внедрение зависимостей в React
Работая с React, мы активно используем пользовательские хуки для извлечения данных, отслеживания поведения пользователей и выполнения сложных вычислений. Излишне говорить, что мы не хотим (и не можем) запускать эти хуки во всех средах.
Отслеживание посещения страницы во время тестирования приведет к повреждению наших аналитических данных, а получение данных из реальной серверной части приведет к медленному выполнению тестов.
Тестирование — не единственная такая среда. Такие платформы, как Storybook, упрощают документацию и могут обойтись без использования многих хуков и бизнес-логики.
Внедрение зависимости через props
Возьмем, к примеру, следующий компонент:
JavaScript import { useTrack } from ‘~/hooks’ function Save() { const { track } = useTrack() const handleClick = () => { console.log(«saving…») track(«saved») } return <button onClick={handleClick}>Save</button> }
123456789101112 | import { useTrack } from ‘~/hooks’ function Save() { const { track } = useTrack() const handleClick = () => { console.log(«saving…») track(«saved») } return <button onClick={handleClick}>Save</button>} |
Как упоминалось ранее, следует избегать использования useTrack (и, в более широком смысле, track). Поэтому преобразуем useTrack в зависимость компонента Save через props:
JavaScript import { useTracker as _useTrack } from ‘~/hooks’ function Save({ useTrack = _useTrack }) { const { track } = useTrack() /* … */ }
1234567 | import { useTracker as _useTrack } from ‘~/hooks’ function Save({ useTrack = _useTrack }) { const { track } = useTrack() /* … */} |
Применяя псевдоним к нашему useTracker, чтобы избежать коллизии имен и используя его в качестве значения по умолчанию для props, мы сохраняем хук в нашем приложении и имеем возможность переопределить его всякий раз, когда возникает необходимость.
React JS. Основы
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Получить курс сейчас!
Имя _useTracker — одно из многих соглашений об именах: useTrackImpl, useTrackImplementation и useTrackDI — все это широко используемые соглашения при попытке избежать коллизии. Внутри Storybook мы можем переопределить хук как таковой, используя имитацию (mock) реализации.
JavaScript import Save from «./Save» export default { component: Save, title: «Save» } const Template = (args) => <Save {…args} /> export const Default = Template.bind({}) Default.args = { useTrack() { return { track() {} } } }
123456789101112131415 | import Save from «./Save» export default { component: Save, title: «Save»} const Template = (args) => <Save {…args} />export const Default = Template.bind({}) Default.args = { useTrack() { return { track() {} } }} |
Использование TypeScript
При работе с TypeScript полезно сообщить другим разработчикам, что внедрения зависимостей props — это именно то, что нужно, и использовать реализацию typeof для сохранения безопасности типов:
JavaScript function App({ useTrack = _useTrack }: Props) { /* … */ } interface Props { /** * For testing and storybook only. */ useTrack?: typeof _useTrack }
12345678910 | function App({ useTrack = _useTrack }: Props) { /* … */} interface Props { /** * For testing and storybook only. */ useTrack?: typeof _useTrack} |
Внедрение зависимости через Context API
Благодаря работе с Context API, внедрение зависимостей позволяет почувствовать себя первоклассным пользователем React. Возможность переопределения контекста, в котором наши хуки запускаются на любом уровне компонента, пригодится при переключении сред.
Многие известные библиотеки предоставляют имитированные реализации своих провайдеров с целью тестирования. React Router v5 имеет MemoryRouter, а Apollo Client предоставляет MockedProvider. Но если мы используем подход, основанный на DI, такие подставные провайдеры не нужны.
React Query — яркий тому пример. Мы можем использовать одного и того же поставщика как для разработки, так и для тестирования и передавать его разным клиентам в каждой среде. В процессе разработки мы можем использовать простой queryClient со всеми параметрами по умолчанию без изменений.
JavaScript import { QueryClient, QueryClientProvider } from «react-query» import { useUserQuery } from «~/api» const queryClient = new QueryClient() function App() { return ( <QueryClientProvider client={queryClient}> <User /> </QueryClientProvider> ) } function User() { const { data } = useUserQuery() return <p>{JSON.stringify(data)}</p> }
1234567891011121314151617 | import { QueryClient, QueryClientProvider } from «react-query»import { useUserQuery } from «~/api» const queryClient = new QueryClient() function App() { return ( <QueryClientProvider client={queryClient}> <User /> </QueryClientProvider> )} function User() { const { data } = useUserQuery() return <p>{JSON.stringify(data)}</p>} |
При тестировании кода такие функции, как retries, re-fetch при фокусировке окна и время кеширования, можно настроить соответствующим образом.
JavaScript // storybook/preview.js import { QueryClient, QueryClientProvider } from «react-query» const queryClient = new QueryClient({ queries: { retry: false, cacheTime: Number.POSITIVE_INFINITY } }) /** @type import(‘@storybook/addons’).DecoratorFunction[] */ export const decorators = [ (Story) => { return ( <QueryClientProvider client={queryClient}> <Story /> </QueryClientProvider> ) }, ]
1234567891011121314151617181920 | // storybook/preview.jsimport { QueryClient, QueryClientProvider } from «react-query» const queryClient = new QueryClient({ queries: { retry: false, cacheTime: Number.POSITIVE_INFINITY }}) /** @type import(‘@storybook/addons’).DecoratorFunction[] */export const decorators = [ (Story) => { return ( <QueryClientProvider client={queryClient}> <Story /> </QueryClientProvider> ) },] |
Внедрение зависимостей в React распространяется не только на хуки, но также и на JSX, JSON и все, что мы хотим абстрагировать или изменить при различных обстоятельствах.
Альтернативы внедрению зависимостей
В зависимости от контекста, внедрение зависимостей может быть неподходящим инструментом для работы. Например, в data-fetching хуках, mock лучше использовать с помощью перехватчика (например, MSW) вместо того, чтобы внедрять хуки по всему тестовому коду, а mocking функции остаются громоздким инструментом для решения более серьезных проблем.
Почему вы должны использовать внедрение зависимостей?
Причины использования DI:
Никаких накладных расходов при разработке, тестировании или производстве
Чрезвычайно легко реализовать
Не требует mocking/stubbing библиотеки, потому что они встроены в JavaScript.
Работает для всех ваших потребностей, таких как компоненты, классы и обычные функции.
Причины не использовать DI:
Загромождает ваш импорт и props / API компонентов
Может сбивать с толку других разработчиков
Заключение
В этой статье мы рассмотрели руководство по внедрению зависимостей в JavaScript и обосновали его использование в React для тестирования и документации. Мы использовали Storybook, чтобы проиллюстрировать использование DI, и, наконец, обсудили причины, по которым вы должны и не должны использовать DI в своем коде.
Автор: Simohamed Marhraoui
Источник: webformyself.com