Я новичок в React и пытаюсь реализовать подобную структуру HTML.
<div class = "heads">
<div class = "head">head1</div>
<div class = "head">head2</div>
<div class = "head">head3</div>
</div>
<div class = "bodies">
<div class = "body">body1</div>
<div class = "body">body2</div>
<div class = "body">body3</div>
</div>
Каждая пара голова-тело (например, head1-body1) принадлежит одному компоненту. Без изменения структуры, как бы я написал такой компонент?
Насколько я понимаю, его нужно "монтировать" в двух разных точках (в "головах" и в "телах").
Практическим примером такой структуры может быть диалоговое окно с вкладками, которое имеет заголовки вкладок в разделе «heads» и их содержимое в «body».
То, что я ищу, это какой-то код, который сможет перевести это:
<Tabs ...>
<Tab head = "head1">body1</Tab>
<Tab head = "head2">body2</Tab>
<Tab head = "head3">body3</Tab>
</Tabs>
в вышеуказанную структуру HTML. Возможно ли это в React?
С добавлением примера, который вы опубликовали: не путайте данные, даже связанные с несколькими частями данных, как «компоненты». Заголовки вкладок — это некоторые данные. Содержимое тела вкладки — это некоторые данные. Существует компонент Tab, который принимает эти данные в качестве реквизита, и компонент Tabs, который отвечает за фокус и макет и отображает активную вкладку. По крайней мере, так я бы сделал, основываясь на довольно простом описании универсального компонента пользовательского интерфейса. Похоже, что со структурой HTML в вашем примере вы бы реализовали это, скрывая/показывая при изменении сфокусированной вкладки, но я бы сделал это совершенно по-другому в React



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Это не совсем естественно для React, но у вас есть несколько вариантов.
Вариант 1. Вы можете стиснуть зубы и визуализировать компоненты головы и тела по отдельности, точно так же, как вы пишете HTML отдельно. Например, Material-UI, одна из популярных библиотек компонентов React, делает это для своего компонента Tabs.
Вариант 2. Компоненты — это просто код, поэтому вы можете манипулировать ими по своему усмотрению. (В следующем примере я придумал несколько уникальных идентификаторов для каждой вкладки, чтобы у меня была поддержка key, чтобы React был доволен, и я также демонстрирую динамическое добавление/удаление элементов.)
function MyPage({hideTab3}) {
const tabs = [
{ id: 1, header: 'head1', body: <MyBody1 /> },
{ id: 2, header: 'head2', body: <MyBody2 /> },
!hideTab3 && { id: 3, header: 'head3', body: <MyBody3 /> },
].filter(Boolean);
return (
<div className = "heads">
{tabs.map(({id, head}) => <div className = "head" key = {id}>{head}</div>)}
</div>
<div className = "bodies">
{tabs.map(({id, body}) => <div className = "body" key = {id}>{body}</div>)}
</div>
)
}
Вариант 3: Вы можете использовать слоты компонентов, которые могут быть простыми или довольно сложными.
Вот пример более сложного подхода — компонент может проверять реквизиты своих дочерних компонентов и самостоятельно обрабатывать их слоты. Здесь я использую TypeScript, чтобы обеспечить соблюдение ограничений для дочерних компонентов.
interface TabProps {
head: ReactNode;
children: ReactNode;
}
function Tab({ head, children }: TabProps) {
return <div className = "body">{children}</div>;
}
interface TabsProps {
// `children` is normally a generic `ReactNode`; specify
// a strong type here, since we manipulate child props.
children:
| ReactElement<TabProps, typeof Tab>
| ReactElement<TabProps, typeof Tab>[];
}
function Tabs({ children }: TabsProps) {
return (
<>
<ul className = "heads">
{Children.map(children, (tab, i) => (
// Tab head loop: Extract out child props, as shown in
// https://sandroroth.com/blog/react-slots#slots-by-type.
//
// Production-ready should maybe throw for non-Tab children.
<li className = "head" key = {i}>
{tab.props.head}
</li>
))}
</ul>
{/* Bodies can show children's default renders. */}
<div className = "bodies">{children}</div>
</>
);
}
export default function App() {
return (
<Tabs>
<Tab head = "head1">body 1</Tab>
<Tab head = "head2">body 2</Tab>
</Tabs>
);
}
Лично я с меньшей вероятностью буду использовать этот подход — как упоминалось в статье sandroroth.com, он может мешать компонуемости (когда один компонент проверяет свойства другого, это больше не работает, если вы начинаете оборачивать и настраивать компоненты), и Я предпочитаю «ясный и компонуемый», а не «волшебный и элегантный». Но это совершенно действующая техника.
Другие варианты: вы можете попробовать более продвинутые методы, такие как использование портала, позволяющего телу отображать свой заголовок в другом месте в DOM — собственный портал React предназначен для рендеринга в узлы, не относящиеся к React, но сторонние компоненты, такие как Портал Material-UI может дать некоторые идеи. Или предоставьте контекст React, который позволяет каждому телу зарегистрироваться через useEffect, чтобы указать, какие вкладки должны отображаться. Эти варианты более сложны и, вероятно, их сложнее поддерживать - я бы не пошел по этому пути, если только другие варианты действительно не будут работать по какой-либо причине.
как новичок, я нахожу довольно странным, что такая простая и полезная структура невозможна без более или менее глупых хаков... Я обновил вопрос, чтобы уточнить, что именно я ищу.
@gog - Да, вы можете делать именно то, что ищете, используя подход со слотами для компонентов. Смотрите мой обновленный ответ.
Одним из возможных способов сделать это может быть создание пользовательского компонента, который принимает любые данные «головы» и «тела», которые вы, возможно, захотите отобразить: (Использование машинописного текста для принудительной проверки типов и предотвращения глупых ошибок)
interface Props {
head: React.ReactNode,
body: React.ReactNode,
}
const CustomComponent = ({head, body}: Props) => {
// add whatever logic you want here
return () => {
<div>{head}{body}</div>
}
}
Вы можете сделать еще один шаг вперед и передать конкретную структуру данных, содержащую кучу данных головы и тела, которые вы хотите визуализировать, и визуализировать их соответственно, как вы считаете нужным.
Не стесняйтесь задавать любые вопросы!
Да, вы можете сделать это легко.
Пожалуйста, обратитесь к приведенной ниже структуре.
const App = () => {
const [tab, setTab] = useState("tab1");
return (
<div>
<div class = "heads">
<div onClick = {()=>{setTab("tab1")}} class = "head">head1</div>,
<div onClick = {()=>{setTab("tab2")}} class = "head">head2</div>,
<div onClick = {()=>{setTab("tab3")}} class = "head">head3</div>
</div>
<div class = "bodies">
{{
"tab1": <div class = "body">body1</div>,
"tab2": <div class = "body">body2</div>,
"tab3": <div class = "body">body3</div>
}[tab]}
</div>
</div>
)
}
Если вам нужны динамические вкладки, вы можете интегрировать их так
const App = () => {
const [tabs, setTabs] = useState([
{head: "Head 1", body: <div>body1</div>},
{head: "Head 2", body: <div>body2</div>},
{head: "Head 3", body: <div>body3</div>},
{head: "Head 4", body: <div>body4</div>},
{head: "Head 5", body: <div>body5</div>},
{head: "Head n", body: <div>bodyn</div>},
]);
const [tab, setTab] = useState(0);
return (
<div>
<div class = "heads">
{
tabs.map((tab, index) => (<div onClick = {()=>{setTab(index)}}>{tab.head}</div>))
}
</div>
<div class = "bodies">
{tabs?.[index]?.body}
</div>
</div>
)
}
И тогда вы получите ту же структуру с
<Tabs ...>
<Tab head = "head1">body1</Tab>
<Tab head = "head2">body2</Tab>
<Tab head = "head3">body3</Tab>
</Tabs>
Просто вам нужно будет добавить CSS
// For example
.heads {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 12px;
border: 1px solid #e0e0e0;
}
.bodies {
padding: 4px;
border: 1px solid #f0f0f0;
}
....
Вместо этого я бы переосмыслил вашу архитектуру... не звучит как идеальный способ ее разложения, но трудно сказать без подробностей.