1. Введение
Код с соответствующими шаблонами проектирования может иметь хорошую ремонтопригодность.The Benefits of Orthogonal React ComponentsВ данной статье основное внимание уделяется принципу ортогональности.
Так называемые ортогональные, то есть модули не влияют друг на друга. Представьте себе, что если нет ортогональной связи между громкостью динамика и кнопкой переключения каналов, управление громкостью может одновременно влиять на смену каналов Такое оборудование сложно обслуживать:
То же самое верно и для внешнего кода.Разделение пользовательского интерфейса и логики обработки данных является дизайном, который соответствует принципу ортогональности, что способствует долгосрочному поддержанию качества кода.
2 Обзор
Приложение React с хорошей ортогональностью будет разработано со следующим разделением модулей:
- Элементы пользовательского интерфейса (презентационные компоненты).
- Логика выборки (библиотека выборки, REST или GraphQL).
- Глобальное управление состоянием (редукс).
- Постоянство (локальное хранилище, файлы cookie).
Текст проиллюстрирован двумя примерами.
Сделайте компоненты ортогональными логике выборки
Например, компонент, отображающий список сотрудников<EmployeesPage>
:
import React, { useState } from "react";
import axios from "axios";
import EmployeesList from "./EmployeesList";
function EmployeesPage() {
const [isFetching, setFetching] = useState(false);
const [employees, setEmployees] = useState([]);
useEffect(function fetch() {
(async function() {
setFetching(true);
const response = await axios.get("/employees");
setEmployees(response.data);
setFetching(false);
})();
}, []);
if (isFetching) {
return <div>Fetching employees....</div>;
}
return <EmployeesList employees={employees} />;
}
Такая конструкция выглядит прекрасно, но на самом деле она нарушает принцип ортогональности, т.к.EmployeesPage
Он отвечает за отрисовку пользовательского интерфейса, а также заботится о логике получения чисел. Ортогональный записывается следующим образом:
import React, { Suspense } from "react";
import EmployeesList from "./EmployeesList";
function EmployeesPage({ resource }) {
return (
<Suspense fallback={<h1>Fetching employees....</h1>}>
<EmployeesFetch resource={resource} />
</Suspense>
);
}
function EmployeesFetch({ resource }) {
const employees = resource.employees.read();
return <EmployeesList employees={employees} />;
}
Suspense
Разделите состояние загрузки на родительский компонент, поэтому дочернему компоненту нужно заботиться только о том, как использовать данные, а не о том, как получить данные (и состояние загрузки).
Сделайте компонент ортогональным прослушивателю прокрутки
Например, компонент, который «прыгает вверх», появляется при прокрутке на определенное расстояние.<ScrollToTop>
, который может быть реализован следующим образом:
import React, { useState, useEffect } from "react";
const DISTANCE = 500;
function ScrollToTop() {
const [crossed, setCrossed] = useState(false);
useEffect(function() {
const handler = () => setCrossed(window.scrollY > DISTANCE);
handler();
window.addEventListener("scroll", handler);
return () => window.removeEventListener("scroll", handler);
}, []);
function onClick() {
window.scrollTo({
top: 0,
behavior: "smooth"
});
}
if (!crossed) {
return null;
}
return <button onClick={onClick}>Jump to top</button>;
}
Как видите, в этом компоненте кнопки и логика определения состояния прокрутки перемешаны. Если бы мы абстрагировали «рендеринг пользовательского интерфейса после прокрутки на определенное расстояние» в общий компонентIfScrollCrossed
Шерстяная ткань?
import { useState, useEffect } from "react";
function useScrollDistance(distance) {
const [crossed, setCrossed] = useState(false);
useEffect(
function() {
const handler = () => setCrossed(window.scrollY > distance);
handler();
window.addEventListener("scroll", handler);
return () => window.removeEventListener("scroll", handler);
},
[distance]
);
return crossed;
}
function IfScrollCrossed({ children, distance }) {
const isBottom = useScrollDistance(distance);
return isBottom ? children : null;
}
имеютIfScrollCrossed
, мы можем сосредоточиться на написании компонента пользовательского интерфейса «нажмите кнопку, чтобы перейти наверх»:
function onClick() {
window.scrollTo({
top: 0,
behavior: "smooth"
});
}
function JumpToTop() {
return <button onClick={onClick}>Jump to top</button>;
}
Наконец, соедините их вместе:
import React from "react";
// ...
const DISTANCE = 500;
function MyComponent() {
// ...
return (
<IfScrollCrossed distance={DISTANCE}>
<JumpToTop />
</IfScrollCrossed>
);
}
сделай это, наш<JumpToTop>
а также<IfScrollCrossed>
Компоненты являются ортогональными отношениями, и логика более понятна. Мало того, такая абстракция заставляет<IfScrollCrossed>
Может быть повторно использован в других сценариях:
import React from "react";
// ...
const DISTANCE_NEWSLETTER = 300;
function OtherComponent() {
// ...
return (
<IfScrollCrossed distance={DISTANCE_NEWSLETTER}>
<SubscribeToNewsletterForm />
</IfScrollCrossed>
);
}
Главный компонент
В приведенном выше примере<MyComponent>
Это Main компонент, Main компонент инкапсулирует какую-то грязную логику, то есть отвечает за сборку разных модулей, и этим модулям не обязательно знать о существовании друг друга.
Приложение будет иметь несколько основных компонентов, которые отвечают за сборку грязной логики в различных областях.
Преимущества ортогонального дизайна
- Простота обслуживания:Ортогональные компоненты логически изолированы друг от друга, и нет необходимости беспокоиться о совместных эффектах, поэтому вы можете уверенно поддерживать один компонент.
- Легко читается:Так как логическое разделение приводит к абстракции, каждый модуль выполняет относительно отдельные действия, и легко догадаться, что делает компонент.
- Тестируемый:Из-за разделения логики идея взлома по одному может быть принята для одиночного тестирования.
компромисс
Без ортогонального дизайна приложение становится трудно поддерживать из-за зависимостей между модулями. Однако, если ортогональный дизайн применяется до крайности, может быть много ненужных абстракций, и эти абстракции повторно используются только один раз, что приводит к чрезмерному проектированию.
3 Интенсивное чтение
Ортогональный дизайн можно понимать как разумную абстракцию в определенной степени, и ни абстракция, ни чрезмерная абстракция не желательны.Поэтому перечислены четыре ключевых момента, которые необходимо абстрагировать: элементы пользовательского интерфейса, логика поиска чисел, глобальное управление состоянием и постоянство.
Внедрение управления глобальным состоянием в компонент представляет собой шаблон ортогональной абстракции, то есть компоненту все равно, откуда берутся данные, а он напрямую использует данные, а управление данными полностью управляется уровнем потока данных.
Логика получения числа часто представляет собой ссылку, которую можно игнорировать, независимо от того, связана ли она непосредственно с исходным текстом.fetch
UI компонент метода или использует количество библиотек инструментов для ухода.loading
условие:
import useSWR from "swr";
function Profile() {
const { data, error } = useSWR("/api/user", fetcher);
if (error) return <div>failed to load</div>;
if (!data) return <div>loading...</div>;
return <div>hello {data.name}!</div>;
}
При инкапсуляции жизненного цикла выборки в настраиваемый хукuseSWR
в, ноerror
Информация — это грязные данные для компонентов пользовательского интерфейса:Это заставляет компонент пользовательского интерфейса не только отображать данные, но и беспокоиться о том, не произойдет ли выборка или она находится в процессе загрузки.
К счастью, режим Suspense решает эту проблему:
import { Suspense } from "react";
import useSWR from "swr";
function Profile() {
const { data } = useSWR("/api/user", fetcher, { suspense: true });
return <div>hello, {data.name}</div>;
}
function App() {
return (
<Suspense fallback={<div>loading...</div>}>
<Profile />
</Suspense>
);
}
так<Profile>
Просто сосредоточьтесь на рендеринге данных, не беспокойтесьuseSWR('/api/user', fetcher, { suspense: true })
Что произошло в процессе извлечения, не удалось ли получить, было ли это вloading
середина. Поскольку состояние выборки задаетсяSuspense
управление, а также то, произойдет ли неожиданный сбой выборки, определяетсяErrorBoundary
управлять.
Разумная абстракция упрощает логику компонентов, так что компоненты можно вкладывать друг в друга, не беспокоясь о дополнительных эффектах. Особенно в крупномасштабных проектах не беспокойтесь о том, что ортогональная абстракция увеличит количество модулей, которых и без того много, потому что по сравнению с поддержкой 100 модулей со сложной внутренней логикой поддержка 200 модулей с четкими обязанностями и взаимной изоляцией может быть проблемой. Полегче.
4 Резюме
С точки зрения ортогонального дизайна,Hooks
Решена проблема разделения управления состоянием и пользовательского интерфейса,Suspense
Решена проблема, связанная с тем, что состояние выборки отделено от пользовательского интерфейса,ErrorBoundary
Решена проблема, из-за которой исключения были отделены от пользовательского интерфейса.
На ваш взгляд, какую еще логику в React нужно отделить от пользовательского интерфейса? Какие методы используются? Добро пожаловать, чтобы оставить сообщение.
Адрес обсуждения:Интенсивное чтение «Ортогональные компоненты React» · Выпуск №221 · dt-fe/weekly
Если вы хотите принять участие в обсуждении, пожалуйста,кликните сюда, с новыми темами каждую неделю, выходящими по выходным или понедельникам. Интерфейс интенсивного чтения — поможет вам отфильтровать надежный контент.
Сфокусируйся наАккаунт WeChat для интенсивного чтения в интерфейсе
Заявление об авторских правах: Бесплатная перепечатка - некоммерческая - не производная - сохранить авторство (Лицензия Creative Commons 3.0)