Что такое принцип открытого-закрытого?
Принцип 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 теперь можно расширять, что позволяет добавлять новые функции без изменения кода.