Внедрение зависимостей в React

0 2

Благодаря наследованию, DI — это широко используемый шаблон в объектно-ориентированном программировании (ООП), предназначенный для многократного использования кода в различных объектах и классах. Однако основной причиной использования внедрения зависимостей в React является простое моделирование и тестирование компонентов. В отличие от Angular, DI не является обязательным требованием при работе с React, это скорее удобный инструмент, который можно использовать, когда вы хотите что-то улучшить.

Внедрение зависимости в JavaScript

Чтобы проиллюстрировать принципы DI, представьте модуль npm, который предоставляет следующую функцию:

JavaScript export const ping = (url) => { return new Promise((res) => { fetch(url) .then(() => res(true)) .catch(() => res(false)) }) }

1234567export const ping = (url) => {  return new Promise((res) => {    fetch(url)      .then(() => res(true))      .catch(() => res(false))  })}

Внедрение зависимостей в React

React JS. Основы

Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения

Получить курс сейчас!

Использование функции ping в современном браузере будет работать нормально.

JavaScript import { ping } from “./ping” ping(“https://logrocket.com”).then((status) => { console.log(status ? “site is up” : “site is down”) })

12345import { 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)) }) }

1234567export 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”) })

123456import 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) } })

1234567891011121314151617const 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> }

123456789101112import { 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() /* … */ }

1234567import { useTracker as _useTrack } from ‘~/hooks’ function Save({ useTrack = _useTrack }) {  const { track } = useTrack()   /* … */}

Применяя псевдоним к нашему useTracker, чтобы избежать коллизии имен и используя его в качестве значения по умолчанию для props, мы сохраняем хук в нашем приложении и имеем возможность переопределить его всякий раз, когда возникает необходимость.

Внедрение зависимостей в React

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() {} } } }

123456789101112131415import 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 }

12345678910function 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> }

1234567891011121314151617import { 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

Оставьте ответ