反应嵌套的手风琴父级不更新高度
P粉293341969
P粉293341969 2024-02-21 15:55:48
[React讨论组]

我正在尝试构建我的主页的移动版本,我的嵌套手风琴“项目”似乎有一个错误,它在第一次打开时无法显示底部项目部分的正确高度。

要打开它,您首先单击项目文本,然后它列出项目,然后单击项目切换项目卡。

(已更新)我相信发生这种情况是因为当子手风琴打开时,我的父手风琴没有重新更新其高度。

你知道这样做的好方法吗?或者如果需要的话,我应该以一种使这成为可能的方式重组我的组件吗?困难在于 Accordion 接受儿童,并且我在其中重复使用 Accordion,所以它相当混乱。我知道我可以使用回调函数来触发父级,但不太确定如何解决这个问题。

主页.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 (
    
) }

投资组合.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 (
    
      {projects.map((project, index) => (
        
          
        
      ))}
    
  )
}

AccordionGroup.tsx - AccordionGroup 的目的是只允许一次打开一个子 Accordion。如果 Accordion 不在 AccordionGroup 中,它可以独立打开和关闭。

"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[]
}

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

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

  return (
    
{Children.map(children, (child, index) => isValidElement(child) ? cloneElement(child, { onClick: () => handleAccordionToggle(index), isActive: activeAccordion === index, children: child.props.children, title: child.props.title, }) : child )}
) }

手风琴.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

  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 (
    
} style={{ maxHeight: height }} >
{children}
) }

ProjectCard.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 (
    
      
{alt}
{title} {tags.map((tag, index) => ( ))} {description}
) } function CoverImage({ children, className }: Children) { return
{children}
} function Tags({ children, className }: Children) { return
{children}
}

如有任何帮助,我们将不胜感激,谢谢!

P粉293341969
P粉293341969

全部回复(1)
P粉649990273

您知道,这里的实现有点具有挑战性,因为当您想要从其子手风琴更新祖父母手风琴的高度时,您无法真正从那里知道您想要更新哪个相应的祖父母手风琴除非您将道具传递给祖父母手风琴,并将道具传递给中间组件(例如,Portfolio,即子手风琴的父级),以便它可以将它们传播到其子手风琴。
通过这样做我们可以让祖父母和孩子手风琴以某种方式进行交流。
也许这不是您能找到的最佳解决方案,但遗憾的是,我想不出更好的解决方案。


所以回顾一下:这个想法是在顶层创建一个状态来保存引用每个父手风琴的高度,所以它是一个数组,其中长度是“手动”设置的,这使得它在某种程度上很难看,但是如果你必须使用数据数组来动态显示您的组件,那么这不是问题,我们稍后会发现,我们也会看到解决方法的限制。


解决方法:

现在我们将采用最简单、最直接的修复方法,该修复方法适用于问题中包含的内容。

如上所述,首先我们在 HomePage 组件中创建状态:

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

在顶层创建数组状态后,现在,我们向每个 Accordion 组件传递状态设置函数 setHeights、索引 indexx 以及相应的height heightParent 如果它是父 Accordion

<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>

注意: 传递给父级的 indexx 属性和传递给中间组件(Portfolio)的 indexx 属性它们应该具有相同的值表示对应的索引,这实际上是解决方案的关键。
命名为“indexx”,其中带有两个“x”,以避免以后发生冲突。

然后,从中间组件将这些收到的道具传递给子手风琴:

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

现在,从您的子 Accordion 组件中,您可以利用传递的 indexx 属性来更新 HomePage 状态下相应 Accordion 父级的高度,因此当我们更新子高度时,我们还更新父高度

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

最后,当您指定一个 Accordion 的高度时,您可以检查它是否接收 heightParent 作为 props,以便我们知道它是父级,这样,我们让 Accordion 组件使用 heightParent 作为 maxHeight 而不是它自己的状态 height(如果它是父状态),这就是忽略更新 height 状态的原因当它是打开的父 Accordion 时,因此,我们必须更改 maxHeight 属性的设置方式,现在它应该取决于 Accordion 的性质:

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

如果您想让父 Accordion 只需使用其状态 height 作为 maxHeight 并保持代码不变,这样更有意义

style={{ maxHeight: height }}

您仍然可以通过在 Accordion 组件中添加 useEffect 并确保其仅在更新并定义接收到的 heightParent 属性时运行来执行此操作,我们这样做确保代码仅在父手风琴应更新其 height 状态时运行:

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

如上所述,这更有意义,而且也最漂亮,但我仍然更喜欢第一个,因为它更简单,并且还节省了一个额外的渲染


动态处理数据:

如果我们将数据存储在数组中,并且希望基于此显示我们的组件,则可以这样做:

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>

您可以注意到,我们必须在父手风琴中指定一个key,以便我们可以使用它而不是indexx,但您知道key > 财产很特殊,无论如何我们都不想弄乱它,希望您明白


限制:

很明显,这个解决方案仅适用于一个级别,因此,如果子手风琴本身成为子手风琴,那么您必须再次围绕它,但如果我理解您在做什么,您可能就不会面对这种情况,因为通过您的实现,子 Accordion 应该显示项目,但谁知道也许有一天您需要让它返回另一个子 Accordion,这就是为什么我认为我的建议是一种解决方法而不是最佳解决方案。


就像我说的,这可能不是最好的解决方案,但说实话,尤其是对于这个实现,我认为不存在这样的多级工作解决方案,请证明我错了,我我正在关注该帖子。

热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号