Rumah  >  Soal Jawab  >  teks badan

React induk akordion bersarang tidak mengemas kini ketinggian

Saya cuba membina versi mudah alih halaman utama saya dan nampaknya terdapat pepijat dengan "item" akordion bersarang saya di mana ia tidak memaparkan ketinggian yang betul bagi bahagian item bawah apabila mula-mula dibuka.

Untuk membukanya, anda mula-mula klik pada teks projek, kemudian ia menyenaraikan projek, kemudian klik pada projek untuk menogol kad projek.

(Dikemas kini) Saya percaya ini berlaku kerana akordion ibu bapa saya tidak mengemas kini ketinggiannya apabila akordion kanak-kanak dibuka.

Adakah anda tahu cara yang baik untuk melakukan ini? Atau jika perlu, patutkah saya menyusun semula komponen saya dengan cara yang membolehkan perkara ini berlaku? Kesukarannya ialah Accordion menerima kanak-kanak, dan saya menggunakan semula Accordion di dalamnya, jadi ia agak mengelirukan. Saya tahu saya boleh menggunakan fungsi panggil balik untuk mencetuskan ibu bapa, tetapi tidak pasti bagaimana untuk melakukannya.

Rumah.tsx

import { Accordion } from "@/components/atoms/Accordion"
import { AccordionGroup } from "@/components/atoms/AccordionGroup"
import { AccordionSlideOut } from "@/components/atoms/AccordionSlideOut"
import { Blog } from "@/components/compositions/Blog"
import { Contact } from "@/components/compositions/Contact"
import { Portfolio } from "@/components/compositions/Portfolio"
import { PuyanWei } from "@/components/compositions/PuyanWei"
import { Resumé } from "@/components/compositions/Resumé"
import { Socials } from "@/components/compositions/Socials"
import { Component } from "@/shared/types"

interface HomepageProps extends Component {}

export function Homepage({ className = "", testId = "homepage" }: HomepageProps) {
  return (
    <main className={`grid grid-cols-12 pt-24 ${className}`} data-testid={testId}>
      <section className="col-span-10 col-start-2">
        <AccordionGroup>
          <Accordion title="Puyan Wei">
            <PuyanWei />
          </Accordion>
          <Accordion className="lg:hidden" title="Portfolio">
            <Portfolio />
          </Accordion>
          <AccordionSlideOut className="hidden lg:flex" title="Portfolio">
            <Portfolio />
          </AccordionSlideOut>
          <Accordion title="Resumé">
            <Resumé />
          </Accordion>
          <Accordion title="Contact">
            <Contact />
          </Accordion>
          <Accordion title="Blog">
            <Blog />
          </Accordion>
          <Accordion title="Socials">
            <Socials />
          </Accordion>
        </AccordionGroup>
      </section>
    </main>
  )
}

portfolio.tsx

import { Accordion } from "@/components/atoms/Accordion"
import { AccordionGroup } from "@/components/atoms/AccordionGroup"
import { ProjectCard } from "@/components/molecules/ProjectCard"
import { projects } from "@/shared/consts"
import { Component } from "@/shared/types"

interface PortfolioProps extends Component {}

export function Portfolio({ className = "", testId = "portfolio" }: PortfolioProps) {
  return (
    <AccordionGroup className={`overflow-hidden ${className}`} testId={testId}>
      {projects.map((project, index) => (
        <Accordion title={project.title} key={`${index}-${project}`} headingSize="h2">
          <ProjectCard project={project} />
        </Accordion>
      ))}
    </AccordionGroup>
  )
}

AccordionGroup.tsx - Tujuan AccordionGroup adalah untuk membenarkan hanya satu anak Accordion dibuka pada satu masa. Jika Accordion tidak berada dalam AccordionGroup, ia boleh dibuka dan ditutup secara berasingan.

"use client"

import React, { Children, ReactElement, cloneElement, isValidElement, useState } from "react"
import { AccordionProps } from "@/components/atoms/Accordion"
import { Component } from "@/shared/types"

interface AccordionGroupProps extends Component {
  children: ReactElement<AccordionProps>[]
}

export function AccordionGroup({
  children,
  className = "",
  testId = "accordion-group",
}: AccordionGroupProps) {
  const [activeAccordion, setActiveAccordion] = useState<number | null>(null)

  function handleAccordionToggle(index: number) {
    setActiveAccordion((prevIndex) => (prevIndex === index ? null : index))
  }

  return (
    <div className={className} data-testid={testId}>
      {Children.map(children, (child, index) =>
        isValidElement(child)
          ? cloneElement(child, {
              onClick: () => handleAccordionToggle(index),
              isActive: activeAccordion === index,
              children: child.props.children,
              title: child.props.title,
            })
          : child
      )}
    </div>
  )
}

akordion.tsx

"use client"
import { Component } from "@/shared/types"
import React, { MutableRefObject, ReactNode, RefObject, useEffect, useRef, useState } from "react"
import { Heading } from "@/components/atoms/Heading"

export interface AccordionProps extends Component {
  title: string
  children: ReactNode
  isActive?: boolean
  onClick?: () => void
  headingSize?: "h1" | "h2"
}

export function Accordion({
  className = "",
  title,
  children,
  isActive,
  onClick,
  headingSize = "h1",
  testId = "Accordion",
}: AccordionProps) {
  const [isOpen, setIsOpen] = useState(false)
  const [height, setHeight] = useState("0px")
  const contentHeight = useRef(null) as MutableRefObject<HTMLElement | null>

  useEffect(() => {
    if (isActive === undefined) return
    isActive ? setHeight(`${contentHeight.current?.scrollHeight}px`) : setHeight("0px")
  }, [isActive])

  function handleToggle() {
    if (!contentHeight?.current) return
    setIsOpen((prevState) => !prevState)
    setHeight(isOpen ? "0px" : `${contentHeight.current.scrollHeight}px`)
    if (onClick) onClick()
  }
  return (
    <div className={`w-full text-lg font-medium text-left focus:outline-none ${className}`}>
      <button onClick={handleToggle} data-testid={testId}>
        <Heading
          className="flex items-center justify-between"
          color={isActive ? "text-blue-200" : "text-white"}
          level={headingSize}
        >
          {title}
        </Heading>
      </button>
      <div
        className={`overflow-hidden transition-max-height duration-250 ease-in-out`}
        ref={contentHeight as RefObject<HTMLDivElement>}
        style={{ maxHeight: height }}
      >
        <div className="pt-2 pb-4">{children}</div>
      </div>
    </div>
  )
}

Kad Projek.tsx

import Image from "next/image"
import { Card } from "@/components/atoms/Card"
import { Children, Component, Project } from "@/shared/types"
import { Subheading } from "@/components/atoms/Subheading"
import { Tag } from "@/components/atoms/Tag"
import { Text } from "@/components/atoms/Text"

interface ProjectCardProps extends Component {
  project: Project
}

export function ProjectCard({
  className = "",
  testId = "project-card",
  project,
}: ProjectCardProps) {
  const {
    title,
    description,
    coverImage: { src, alt, height, width },
    tags,
  } = project
  return (
    <Card className={`flex min-h-[300px] ${className}`} data-testid={testId}>
      <div className="w-1/2">
        <CoverImage className="relative w-full h-full mb-4 -mx-6-mt-6">
          <Image
            className="absolute inset-0 object-cover object-center w-full h-full rounded-l-md"
            src={src}
            alt={alt}
            width={parseInt(width)}
            height={parseInt(height)}
            loading="eager"
          />
        </CoverImage>
      </div>
      <div className="w-1/2 p-4 px-8 text-left">
        <Subheading className="text-3xl font-bold" color="text-black">
          {title}
        </Subheading>
        <Tags className="flex flex-wrap pb-2">
          {tags.map((tag, index) => (
            <Tag className="mt-2 mr-2" key={`${index}-${tag}`} text={tag} />
          ))}
        </Tags>
        <Text color="text-black" className="text-sm">
          {description}
        </Text>
      </div>
    </Card>
  )
}

function CoverImage({ children, className }: Children) {
  return <div className={className}>{children}</div>
}
function Tags({ children, className }: Children) {
  return <div className={className}>{children}</div>
}

Sebarang bantuan akan sangat dihargai, terima kasih!

P粉293341969P粉293341969212 hari yang lalu272

membalas semua(2)saya akan balas

  • P粉998100648

    P粉9981006482024-02-22 10:41:59

    Analisis masalah:

    TL;DR: Akordion induk perlu mengetahui tentang perubahan ini supaya ia boleh menyesuaikan ketinggiannya dengan sewajarnya.

    Saya rasa anda mungkin menggunakan amiut/accordionify seperti yang ditunjukkan melalui "Mencipta React Accordions yang ringan" daripada Amin A. Rezapour.
    Ini adalah satu-satunya projek yang saya temui yang menggunakan AccordionGroup.

    Struktur akordion bersarang dalam aplikasi melibatkan hubungan ibu bapa-anak, di mana ketinggian akordion kanak-kanak berubah secara dinamik bergantung pada sama ada ia mengembang atau runtuh.

    Ini boleh digambarkan oleh Portfolio.tsx anda, di mana komponen Portfolio.tsx 来说明,其中 AccordionGroup 组件包含多个基于 创建的 Accordion 组件项目 数组。这些 Accordion mengandungi berbilang tatasusunan Accordion item komponen yang dibuat berdasarkan . Komponen Accordion ini ialah akordion "kanak-kanak" yang disebut:

    export function Portfolio({ className = "", testId = "portfolio" }: PortfolioProps) {
      return (
        <AccordionGroup className={`overflow-hidden ${className}`} testId={testId}>
          {projects.map((project, index) => (
            <Accordion title={project.title} key={`${index}-${project}`} headingSize="h2">
              <ProjectCard project={project} />
            </Accordion>
          ))}
        </AccordionGroup>
      )
    }
    

    Setiap subAccordion 都包含一个显示项目详细信息的ProjectCard。当用户单击Accordion(或“项目”)时,它会展开以显示ProjectCard.
    Di sinilah perubahan ketinggian berlaku; akordion akan mengembang atau runtuh berdasarkan interaksi pengguna, mengubah ketinggiannya secara dinamik.

    Ketinggian dinamik diuruskan dalam Accordion.tsx:

      function handleToggle() {
        if (!contentHeight?.current) return
        setIsOpen((prevState) => !prevState)
        setHeight(isOpen ? "0px" : `${contentHeight.current.scrollHeight}px`)
        if (onClick) onClick()
      }
    

    apabila dipanggilhandleToggle函数时,它会检查手风琴当前是否打开(isOpen). Jika ya, ketinggian ditetapkan kepada "0px" (iaitu akordion runtuh). Jika tidak dibuka, ketinggian ditetapkan kepada ketinggian tatal kandungan (iaitu akordion dikembangkan).

    Perubahan ketinggian dinamik akordion kanak-kanak ini adalah bahagian utama masalah anda. Akordion induk perlu mengetahui tentang perubahan ini supaya ia boleh menyesuaikan ketinggiannya dengan sewajarnya.

    Saya melihat dalam yang sama Accordion.tsx:

      useEffect(() => {
        if (isActive === undefined) return
        isActive ? setHeight(`${contentHeight.current?.scrollHeight}px`) : setHeight("0px")
      }, [isActive])
    

    Ketinggian akordion adalah berdasarkan isActive 属性设置的,它表示手风琴当前是否打开。如果打开,则高度设置为手风琴内容的滚动高度(有效展开手风琴),如果未激活,则高度设置为 0px (akordion lipat).

    Walau bagaimanapun, sementara kesan ini melaraskan ketinggian setiap akordion dengan betul berdasarkan keadaannya sendiri isActive, ia tidak mengambil kira perubahan dalam ketinggian akordion kanak-kanak.

    Apabila akordion bersarang (anak) menukar ketinggiannya (disebabkan pengembangan atau keruntuhan), ketinggian akordion induk tidak dikira semula dan oleh itu tidak menyesuaikan diri agar sesuai dengan ketinggian baru anak-anak ibu bapa.

    Dalam erti kata lain, apabila ketinggian akordion kanak-kanak berubah, akordion ibu bapa tidak tahu bahawa ia perlu membuat semula dan menyesuaikan ketinggiannya. Kekurangan pemaparan semula apabila akordion bersarang dibesarkan atau diruntuhkan menyebabkan akordion induk tidak memaparkan ketinggian yang betul.

    Penyelesaian yang mungkin

    TL;DR: Penyelesaiannya melibatkan menyedarkan ibu bapa tentang perubahan ketinggian akordion anak supaya ia boleh menyesuaikan ketinggiannya sendiri dengan sewajarnya.

    (“Reaksi: Salah satu teknik yang disebut dalam pemaparan semula komponen Force | 4 cara mudah ” oleh Josip Miskovic)

    Harta Accordion 组件可以受益于回调函数 prop,该函数在其高度发生变化时被调用,例如 onHeightChange。然后,在 Portfolio 组件中,您可以通过将新的回调函数传递给 Accordion 组件来将此高度更改向上传播到 Homepage 组件,利用 onHeightChange anda.

    手风琴.tsx

    export interface AccordionProps extends Component {
      title: string
      children: ReactNode
      isActive?: boolean
      onClick?: () => void
      onHeightChange?: () => void
      headingSize?: "h1" | "h2"
    }
    
    export function Accordion({
      className = "",
      title,
      children,
      isActive,
      onClick,
      onHeightChange,
      headingSize = "h1",
      testId = "Accordion",
    }: AccordionProps) {
      // ...
    
      useEffect(() => {
        if (isActive === undefined) return
        isActive ? setHeight(`${contentHeight.current?.scrollHeight}px`) : setHeight("0px")
        if(onHeightChange) onHeightChange() // Call the onHeightChange callback
      }, [isActive])
    
      // ...
    }
    

    Kemudian ubah suai komponen Portfolio anda untuk menyebarkan peristiwa perubahan ketinggian:

    export function Portfolio({ className = "", testId = "portfolio", onHeightChange }: PortfolioProps & {onHeightChange?: () => void}) {
      return (
        <AccordionGroup className={`overflow-hidden ${className}`} testId={testId}>
          {projects.map((project, index) => (
            <Accordion 
              title={project.title} 
              key={`${index}-${project}`} 
              headingSize="h2"
              onHeightChange={onHeightChange} // Propagate the height change event
            >
              <ProjectCard project={project} />
            </Accordion>
          ))}
        </AccordionGroup>
      )
    }
    

    Akhir sekali, anda boleh menambah kunci pada akordion Portfolio pada halaman utama yang akan berubah apabila peristiwa perubahan ketinggian dicetuskan. Ini akan menyebabkan akordion untuk membuat semula:

    export function Homepage({ className = "", testId = "homepage" }: HomepageProps) {
      const [key, setKey] = useState(Math.random());
      //...
    
      return (
        //...
        <Accordion className="lg:hidden" title="Portfolio" key={key}>
          <Portfolio onHeightChange={() => setKey(Math.random())} />
        </Accordion>
        //...
      )
    }
    

    Dengan cara ini, anda akan memaksa komponen induk Accordion untuk membuat semula apabila ketinggian anak Accordion berubah.

    balas
    0
  • P粉649990273

    P粉6499902732024-02-22 10:08:40

    Anda tahu, pelaksanaan di sini agak mencabar kerana apabila anda ingin mengemas kini ketinggian akordion datuk dan nenek daripada akordion anaknya, anda tidak boleh benar-benar tahu dari sana akordion datuk dan nenek yang sepadan yang anda ingin kemas kini melainkan anda menyerahkan prop kepada akordion datuk nenek, dan hantar prop kepada komponen perantaraan (cth., Portfolio, induk akordion kanak-kanak) supaya ia boleh membiaknya kepada akordion anaknya.
    Dengan melakukan ini, kami boleh membenarkan datuk dan nenek dan anak-anak akordion berkomunikasi dalam beberapa cara.
    Mungkin ini bukan penyelesaian terbaik yang boleh anda temui, tetapi malangnya saya tidak dapat memikirkan penyelesaian yang lebih baik.


    Jadi untuk mengimbas kembali: ideanya adalah untuk mencipta keadaan di peringkat atas untuk memegang rujukan kepada ketinggian setiap akordion induk, jadi ia adalah tatasusunan di mana panjang ditetapkan "secara manual", yang menjadikannya agak hodoh, tetapi Jika anda perlu menggunakan tatasusunan data untuk memaparkan komponen anda secara dinamik maka ini tidak menjadi masalah, kerana kita akan mengetahui kemudian, kita juga akan melihat batasan penyelesaian.


    Penyelesaian:

    Sekarang kita akan menggunakan pembetulan paling mudah dan paling mudah yang sesuai untuk perkara yang disertakan dalam soalan.

    Seperti yang dinyatakan di atas, mula-mula kita cipta keadaan dalam komponen Halaman Utama:

    const [heights, setHeights] = useState(Array(7).fill("0px")); // you have 7 parent Accordions
    

    Selepas mencipta keadaan tatasusunan di peringkat atas, kini, kami menghantar fungsi tetapan keadaan kepada setiap komponen Akordion setHeights、索引 indexx 以及相应的height heightParent jika ia adalah Akordion induk

    <AccordionGroup>
      <Accordion title="Puyan Wei" heightParent={heights[0]} setHeights={setHeights} indexx="0">
          <PuyanWei setHeights={setHeights} indexx="0" />
      </Accordion>
      <Accordion title="Portfolio" heightParent={heights[1]} setHeights={setHeights} indexx="1">
          <Portfolio setHeights={setHeights} indexx="1" />
      </Accordion>
      //...
      <Accordion title="Socials" heightParent={heights[6]} setHeights={setHeights} indexx="6">
          <Socials setHeights={setHeights} indexx="6" />
      </Accordion> 
    </AccordionGroup>
    

    NOTA: Sifat indexx 属性和传递给中间组件(Portfolio)的 indexx yang diserahkan kepada induk sepatutnya mempunyai nilai yang sama menunjukkan indeks yang sepadan, yang sebenarnya merupakan kunci kepada penyelesaian.
    Namakan "indexx" dengan dua "x" di dalamnya untuk mengelakkan konflik kemudian.

    Kemudian, hantar prop yang diterima ini kepada akordion kanak-kanak dari komponen tengah:

    export function Portfolio({
      className = "",
      testId = "portfolio",
      indexx,
      setHeight,
    }: PortfolioProps) {
      // update props interface
      return (
        <AccordionGroup className={`overflow-hidden ${className}`} testId={testId}>
          {projects.map((project, index) => (
            <Accordion
              title={project.title}
              key={`${index}-${project}`}
              headingSize="h2"
              indexx={indexx}
              setHeight={setHeight}
            >
              <ProjectCard project={project} />
            </Accordion>
          ))}
        </AccordionGroup>
      );
    }
    

    Kini, daripada komponen Akordion anak anda, anda boleh menggunakan harta indexx yang diluluskan untuk mengemas kini ketinggian induk Accordion yang sepadan dalam keadaan Halaman Utama, jadi apabila kami mengemas kini ketinggian anak, kami juga mengemas kini ketinggian induk

    function handleToggle() {
      if (!contentHeight?.current) return;
      setIsOpen((prevState) => !prevState);
      let newHeight = isOpen ? "0px" : `${contentHeight.current.scrollHeight}px`;
      if (!heightParent) { // there is no need to update the state when it is a parent Accordion
        setHeight(newHeight);
      }
      setHeights((prev) =>
        prev.map((h, index) => (index == indexx ? newHeight : h))
      );
    }
    

    Akhir sekali, apabila anda menentukan ketinggian Akordion anda boleh menyemak sama ada ia menerima heightParent 作为 props,以便我们知道它是父级,这样,我们让 Accordion 组件使用 heightParent 作为 maxHeight 而不是它自己的状态 height(如果它是父状态),这就是忽略更新 height 状态的原因当它是打开的父 Accordion 时,因此,我们必须更改 maxHeight cara harta itu ditetapkan, kini ia harus bergantung pada sifat Akordion:

    style={{ maxHeight: `${heightParent? heightParent : height }` }}
    

    Jika anda mahukan Accordion induk hanya gunakan keadaannya height 作为 maxHeight dan pastikan kodnya sama, ia lebih masuk akal

    style={{ maxHeight: height }}
    

    Anda masih boleh menjalankannya dengan menambah status useEffect 并确保其仅在更新并定义接收到的 heightParent 属性时运行来执行此操作,我们这样做确保代码仅在父手风琴应更新其 height dalam komponen Akordion:

    useEffect(()=>{
     if(heightParent){
       setHeight(heightParent)
     }
    },[heightParent])
    

    Seperti yang dinyatakan di atas, yang ini lebih masuk akal dan juga paling cantik, tetapi saya masih lebih suka yang pertama kerana ia lebih ringkas dan juga menjimatkan render tambahan.


    Pemprosesan data dinamik:

    Jika kami menyimpan data dalam tatasusunan dan ingin memaparkan komponen kami berdasarkan ini, kami boleh melakukan ini:

    const data = [...]
    const [heights, setHeights] = useState(data.map(_ => "0px")); 
    //...
    <section className="col-span-10 col-start-2">
     <AccordionGroup>
      {data.map(element, index => {
         <Accordion key={index} title={element.title} heightParent={heights[index]} setHeights={setHeights} indexx={index} >
           {element.for === "portfolio" ? <Portfolio setHeights={setHeights} indexx={index} /> : <PuyanWei setHeights={setHeights} indexx={index} /> }  // just an example
         </Accordion>
      })
      }
     </AccordionGroup>
    </section>
    

    Anda boleh perasan bahawa kami perlu menentukan key,以便我们可以使用它而不是indexx,但您知道key > harta dalam akordion induk dan kami tidak mahu mengacaukannya, harap anda faham


    Keterbatasan:

    Jelas sekali penyelesaian ini hanya berfungsi untuk satu tahap, jadi jika sub-akordion itu sendiri menjadi sub-akordion anda perlu membungkusnya semula, tetapi jika saya faham apa yang anda lakukan, anda mungkin tidak akan menghadapi ini, Kerana dengan pelaksanaan anda kanak-kanak Accordion harus menunjukkan item itu, tetapi siapa tahu mungkin suatu hari nanti anda perlu mengembalikannya lagi anak Accordion, itulah sebabnya saya fikir cadangan saya adalah penyelesaian dan bukan penyelesaian terbaik.


    Seperti yang saya katakan, ini mungkin bukan penyelesaian terbaik, tetapi sejujurnya, terutamanya untuk pelaksanaan ini, Saya tidak fikir penyelesaian kerja pelbagai peringkat sedemikian wujud, sila buktikan saya salah, saya mengikuti siaran itu .

    balas
    0
  • Batalbalas