Что такое принцип открытого-закрытого?

Принцип Open Close — это принцип проектирования программного обеспечения, согласно которому объекты программного обеспечения (классы, модули, функции и т. д.) должны быть расширяемыми, но не модифицируемыми.

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

Предположим, у вас есть ящик для игрушек, наполненный различными игрушками. Ящик для игрушек функционирует аналогично программному объекту. Вы можете продолжать добавлять новые игрушки в ящик для игрушек (расширять его), не меняя сам ящик для игрушек. Однако вам не нужно открывать коробку с игрушками и менять ее структуру или конструкцию каждый раз, когда вы хотите добавить новую игрушку (модифицировать ее).

Как реализовать OCP в React?

Один из способов реализовать принцип Open Close в React — использовать компоненты высшего порядка (HOC).

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

Вот пример компонента React, который нарушает принцип открытого-закрытого:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    async function fetchData() {
      setLoading(true);
      try {
        const response = await axios.get('/api/users');
        setUsers(response.data);
      } catch (err) {
        setError(err);
      }
      setLoading(false);
    }
    fetchData();
  }, []);
  
  if (loading) {
    return <p>Loading...</p>;
  }
  
  if (error) {
    return <p>Error: {error.message}</p>;
  }
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Этот компонент отображает список пользователей, полученных через вызов API. Он разделен на три состояния: loadin, error и users. Когда компонент монтируется, он извлекает данные из API и показывает счетчик загрузки или сообщение об ошибке, если есть проблема.

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

Чтобы решить эту проблему, следуя принципу «открыто-закрыто», вы можете разделить функциональность API и управление состоянием на отдельный пользовательский хук, который можно легко расширить, не переписывая его код.

Вот переработанная версия UserList компонента, который следует принципу Open Close:

import React from 'react';
import useApi from './useApi';

function UserList() {
  const { data: users, loading, error } = useApi('/api/users');
  
  if (loading) {
    return <p>Loading...</p>;
  }
  
  if (error) {
    return <p>Error: {error.message}</p>;
  }
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

useApi Хук — это пользовательский хук, который управляет логикой и состоянием API. Он принимает URL-адрес в качестве входных данных и создает объект, содержащий состояния data, loading и error.

Хук useApi a реализован следующим образом:

import { useState, useEffect } from 'react';
import axios from 'axios';

function useApi(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    async function fetchData() {
      setLoading(true);
      try {
        const response = await axios.get(url);
        setData(response.data);
      } catch (err) {
        setError(err);
      }
      setLoading(false);
    }
    fetchData();
  }, [url]);
  
  return { data, loading, error };
}

export default useApi;

Теперь вы можете просто добавлять новые функции в компонент UserList, не изменяя его код. Например, вы можете использовать хук useApi, чтобы добавить фильтр в список пользователей, как показано ниже:

import React from 'react';
import useApi from './useApi';

function UserList() {
  const [filter, setFilter] = useState('');
  const { data: users, loading, error } = useApi(`/api/users?name=${filter}`);
  
  if (loading) {
    return <p>Loading...</p>;
  }
  
  if (error) {
    return <p>Error: {error.message}</p>;
  }
  
  return (
    <>
      <input type="text" value={filter} onChange={event => setFilter(event.target.value)} />
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </>
  );
}

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