[Перевод] Использование шаблонов проектирования группы GoF в React

В этой статье поговорим о том, как написать в React многократно используемый код, используя три шаблона проектирования группы Gang-of-Four.

39d3d92a607b84187e031e3406cdf437.jpeg

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

Чтобы повысить производительность процесса, разработчикам важно понимать принципы работы шаблонов проектирования и особенности их использования.

В этой статье мы обсудим шаблоны проектирования группы Gang-of-Four и способы их реализации с помощью библиотеки React.

Gang-of-Four

Как уже упоминалось, шаблоны проектирования помогают найти решение типичных проблем при разработке программного обеспечения, независимое от языка, многократно используемое и масштабируемое. Название Gang of Four — «Банда четырех» — относится к четырем авторам, написавшим книгу «Design Patterns: Elements of Reusable Object-Oriented Software», в которой они предложили 23 шаблона проектирования, основанные на принципах объектно-ориентированного программирования.

Четыре автора разделили шаблоны проектирования на три группы.

1. Порождающие шаблоны позволяют создать экземпляр какого-либо класса (создать компонент), скрывая при этом логику его создания.

2. Структурные шаблоны помогают эффективно определять отношения между классами (компонентами).

3. Поведенческие шаблоны обеспечивают взаимодействие между компонентами.

Все эти шаблоны не зависят от языка. Разработчики могут адаптировать их к различным библиотекам и фреймворкам, таким как React например.

Использование шаблонов в React

Итак, у нас есть общее представление о шаблонах проектирования и о том, почему разработчикам стоит их использовать. Поэтому давайте посмотрим, как мы можем использовать эти шаблоны проектирования в React.

Для наглядности я рекомендую использовать React с TypeScript, так как это статически типизированный язык с интерфейсами, наследованием и поддержкой типов: все это помогает быстро приступить к использованию шаблонов.

Требования

Убедитесь, что у вас установлен Node.js. Вы можете набрать node -v, чтобы подтвердить установку. Если вы успешно установили Node.js, вы увидите такие строки.

Подтверждение установки Node.jsПодтверждение установки Node.js

После этого наберите команду ниже чтобы создать проект React на TypeScript под названием «react-design-patterns.»

npx create-react-app react-design-patterns -template typescript

Singleton

Singleton — это порождающий шаблон, который гарантирует, что у класса будет только один экземпляр с одной точкой доступа.

Когда его стоит применять?

Например, предположим, что приложение React использует глобальный объект конфигурации, содержащий информацию о вошедшем в систему пользователе. Приложение должно повторно использовать эту информацию для получения информации о текущем пользователе в приложении.

Это идеальный сценарий, в котором разработчики могут использовать шаблон singleton.

Singleton в React

При реализации этого шаблона мы должны учитывать два условия.

1. Класс должен иметь только один элемент;

2. Он должен быть доступен только с одной точки.

В React разработчики могут использовать настраиваемый модуль управления для выполнения этих двух условий. Например, код ниже.

// user type
interface SingletonConfigValues {
 name?: string;
 id?: string;
 email?: string;
 token?: string;
}

// private variable accessible only within current module
let loggedInUserStore: SingletonConfigValues | undefined = undefined;
const userActions = {
 // configure the single instance logged in user
 initializeUser: (user: SingletonConfigValues) => {
   loggedInUserStore = user;
 },
 // retrieve the single instance of the logged in user
 getUserInformation: () => {
   return loggedInUserStore;
 }
};

// export the methods so that components can use the single instance
export default userActions;

Фрагмент выше показывает модуль TypeScript, который управляет состоянием единого экземпляра частной переменной — loggedInUserStore, используя два метода: getUserInformation — для получения единственного елемента и initializeUser — для его настройки.

Этот модуль обеспечивает одиночный подход к управлению глобальной пользовательской информацией. Чтобы использовать этот модуль в компоненте, импортируйте действия и вызовите методы, как показано ниже.

import { useEffect, useState } from "react";
// obtain the single instance - directly user var is not accessible (private)
import userRetriever from '../../store/custom-singleton';

export const ComponentA = () => {
  const [userInformation, setUserInformation] = useState(undefined);
  useEffect(() => {
    setUserInformation(userRetriever.getUserInformation());
  }, [userInformation]);
  return (
    

Component A

{userInformation && ( <> Name: {userInformation.name}
Id: {userInformation.id}
Email: {userInformation.email}
Token: {userInformation.token} }

/div> ; };

Фрагмент выше показывает компонент, который возвращает единственный элемент.

import { useEffect } from "react";
import { ComponentA } from "./component-a";
// retrieve the single instance - directly user var is not acessible (private)
import userInformation from '../../store/custom-singleton';

export const Singleton = () => {

  useEffect(() => {
    // initialize the single instance variable
    userInformation.initializeUser({
      name: 'John Doe',
      id: '123',
      email: 'johndoe@gmail.com',
      token: '123456789'
    })
  }, []);

  return (
    
); };

Фрагмент выше показывает единственный элемент пользователя, создаваемый в useEffect.

e1d2486dfe79ec43b8ebf9ebfc2125db.png

О преимуществах и недостатках

Использование этого шаблона позволяет эффективно масштабировать и поддерживать код, избежать его дублирования в нескольких компонентах.

Основным недостатком шаблона является то, что он делает код менее пригодным для тестирования, поскольку между модульными тестами глобальное состояние остается неизменным.

Observer или наблюдатель

Это поведенческий шаблон, который помогает определить механизм подписки для уведомления наблюдателей (компоненты) о любых изменениях объекта наблюдения.

Наиболее подходящий вариант использования в приложении React — когда приложение зависит от событий браузера. Например, в приложении React есть компонент, которому необходимо переключать свою функциональность в зависимости от доступности Интернета. Компонент может подписаться на события браузера, получать уведомления об изменениях и обновлять свое состояние в соответствии с ними.

Observer в React

При использовании шаблона:

1. Компонент должен сначала подписаться на событие.

2. Затем, когда компонент размонтируется, от события нужно отписаться.

Рассмотрим код ниже.

import { useEffect, useState } from "react";

export const InternetAvailabilityObserver = () => {
  const [isOnline, setOnline] = useState(navigator.onLine);

  useEffect(() => {
    // subscribe to two events -> online and offline
    
    // when online -> set online to true

    // when offline -> set online to false

    window.addEventListener("online", () => setOnline(true));  
    window.addEventListener("offline", () => setOnline(false));
    return () => {
      // when component gets unmounted, remove the event listeners to prevent memory leaks

      window.removeEventListener("online", () => setOnline(true)); 
      window.removeEventListener("offline", () => setOnline(false));
    };
  }, []);

  return (
    <>

Internet Availability Observer

{isOnline ? ( <> You are online ) : ( <> You are offline )}

); };

На фрагменте выше показано использование шаблона в React. Он создает две подписки с двумя субъектами (онлайн- и офлайн- события). И когда субъекты уведомляют о каких-либо изменениях, для обновления состояния компонента выполняются функции обратного вызова.

Observer implementation with ReactObserver implementation with React

О преимуществах и недостатках

В качестве преимущества можно отметить, что он разделяет код между субъектом и наблюдателем, сопровождая код. Так же несколько наблюдателей могут подписаться и отказаться от подписки на субъект в любой момент.

К недостаткам можно случай, если при размонтировании компонента не удалить события, на которые вы были подписаны, React может продолжать отслеживать изменения, что приведет к утечке памяти и потере производительности.

Facade

Это структурный шаблон, цель которого предоставить упрощенный способ взаимодействия с несколькими компонентами через единый API. Кроме того, он скрывает сложность внутренних операций, делая код более читабельным. Для реализации этого шаблона вам потребуются интерфейсы и классы. Но вы можете использовать основной принцип этого шаблона и применить его в React.

Как его использовать?

Вы можете использовать этот шаблон в приложениях React, когда структура приложения становится сложной. Хорошим примером использования шаблона в React является управление состоянием компонента.

Например, у вас может быть компонент, который управляет пользователями приложения (добавление, удаление, выборка). Как правило, это приводит к большому количеству переменных состояния и HTTP-запросов, которые загромождают код и ухудшают его читабельность. Рассмотрим фрагмент ниже.

export const NoFacade: FC = () => {
  const [users, setUsers] = useState([]);

  const fetchUsers = useCallback(async () => {
    const resp = await axios.get(`/api/users`);
    setUsers(resp.data);
  }, []);

  useEffect(() => {
    fetchUsers();
  }, [fetchUsers]);

  const handleUserDelete = async (id: string) => {
    await axios.delete(`/api/users/${id}`);
    setUsers(users.filter((user: any) => user.id !== id));
  };

  const handleCreateUser = async (user: any) => {
    if (!users.find((u: any) => u.id === user.id)) {
      await axios.post(`/api/users`, user);
      setUsers([...users, user]);
    } else {
      console.log("User already exists");
    }
  };
  return (
    <>

  );
};

На первый взгляд фрагмент сложно прочитать, так как он состоит из сложных HTTP-запросов и переменных состояния. Однако разработчики могут сгруппировать сложные HTTP-запросы и переменные состояния и оставить единый API для управления данными. В таких случаях можно использовать этот шаблон.

В React разработчики могут прийти к тому же результату, сгруппировав сложный код в кастомный хук и передав его во владение клиентов.

Facade в React

Улучшим фрагмент, приведенный выше, заменив сложный код на кастомный хук. Он будет содержать методы для создания, извлечения и удаления пользователей. Компоненты могут применять методы для использования функциональности, не просматривая сложный код, который выполняется последовательно.

Код кастомного элемента показан ниже.

import axios from "axios";
import { useState } from "react";

export const useFacadeUserAPI = () => {
  const [users, setUsers] = useState([]);
  const [actionExecuting, setActionExecuting] = useState(false);

  // expose one method to get users

 async function getUsers() {setActionExecuting(true);
    try {
      const resp = await axios.get("/api/users");
      setUsers(resp.data);
    } catch (err) {
      console.log(err);
    } finally {
      setActionExecuting(false);
    }
  }

  // expose method to create user

  async function createUser(user: any) {setActionExecuting(true);
    try {
      await axios.post("/api/users", user);
      setUsers([...users, user]);
    } catch (err) {
      console.log(err);
    } finally {
      setActionExecuting(false);
    }
  }
  
  // expose method to delete a user

  async function deleteUser(id: string) {setActionExecuting(true);
    try {
      await axios.delete(`/api/users/${id}`);
      setUsers(users.filter((user: any) => user.id !== id));
    } catch (err) {
      console.log(err);
    } finally {
      setActionExecuting(false);
    }
  }

  // return the methods that encapsulate the complex code// resulting 
  in a cleaner client code

   return {// the users

     users, 
    // boolean to indicate if action occurs
    
     actionExecuting,
    //  method to mutate the users via HTTP requests
    
     getUsers, 
     createUser,
     deleteUser,
  };
};

На фрагменте выше показан кастомный хук, который отвечает за управление пользователями. Он возвращает набор методов, которые компоненты могут использовать для взаимодействия с API.

Обновленный код компонента показан ниже.

export const Facade: FC = () => {  
  const userFacade = useFacadeUserAPI();  
  const { createUser, deleteUser, getUsers, users } = userFacade;
  
  const fetchUsers = useCallback(async () => {
      // replace with facade API method to simplify code
      await getUsers();
  }, [getUsers]);
  
  useEffect(() => {
      fetchUsers();
  }, [fetchUsers]);
  
  const handleUserDelete = async (id: string) => {
      // replace with Facade method to hide complex code required in deleting
      await deleteUser(id);
  };
  
  const handleCreateUser = async (user: any) => {
      // replace with a facade method to hide complex code required in creating
      await createUser(user);
  };
  return (
      <>

  );
  
};

На фрагменте выше показан обновленный код для компонента. Кастомный хук менее запутанным и более понятным, а также полностью скрывает всю сложную бизнес-логику компонента.

О преимуществах и недостатках

В плюс то, что он позволяет повторно использовать код, что помогает избежать его дублирования, а также разделяет бизнес-логику компонента, позволяя разработчикам писать модульные тесты для функций.

При этом, трудно перепроектировать существующий код и заключить его в кастомный хук.

Но при правильной реализации шаблон создает переиспользуемый и масштабируемый код.

Заключение

В этой статье мы рассмотрели шаблоны проектирования и то, как разработчики могут использовать их в библиотеке React для совершенствования фронтенд-разработки. Шаблоны проектирования, реализованные в этой статье, доступны в репозитории.

© Habrahabr.ru