Компонент, установленный в двух точках

Я новичок в 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?

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

Jared Smith 21.04.2023 20:25

С добавлением примера, который вы опубликовали: не путайте данные, даже связанные с несколькими частями данных, как «компоненты». Заголовки вкладок — это некоторые данные. Содержимое тела вкладки — это некоторые данные. Существует компонент Tab, который принимает эти данные в качестве реквизита, и компонент Tabs, который отвечает за фокус и макет и отображает активную вкладку. По крайней мере, так я бы сделал, основываясь на довольно простом описании универсального компонента пользовательского интерфейса. Похоже, что со структурой HTML в вашем примере вы бы реализовали это, скрывая/показывая при изменении сфокусированной вкладки, но я бы сделал это совершенно по-другому в React

Jared Smith 21.04.2023 20:37
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
3
2
188
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Это не совсем естественно для 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 22.04.2023 09:38

@gog - Да, вы можете делать именно то, что ищете, используя подход со слотами для компонентов. Смотрите мой обновленный ответ.

Josh Kelley 24.04.2023 05:08

Одним из возможных способов сделать это может быть создание пользовательского компонента, который принимает любые данные «головы» и «тела», которые вы, возможно, захотите отобразить: (Использование машинописного текста для принудительной проверки типов и предотвращения глупых ошибок)

 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;
}
....

Другие вопросы по теме