如何从头开始建立一个React手风琴菜单

689 阅读7分钟

手风琴菜单,也叫扩展面板,是一个允许用户在用户界面中隐藏和显示内容之间进行切换的功能。

垂直堆叠的面板包含了最重要的信息,当点击一个面板时,它就会扩展以显示更多的相关内容,这个概念被称为渐进式披露。

手风琴菜单体现了渐进式披露,它提供了一个简洁的用户界面,并最大限度地减少了滚动,尤其是在较小的屏幕上,这对移动设备是有利的。

在本教程中,我们将介绍如何在React中通过创建一个FAQ应用程序从头开始构建一个手风琴菜单。

Final Accordion FAQ Application Layout With One Panel Open

开始学习

在开始本教程之前,请确保你对React 和Node.js基本的了解。你可以在这里看到最终的项目

现在,让我们开始吧。

创建一个新的React项目

从你想保存项目的目录中运行以下命令(例如,cd Desktop )。

npx create-react-app react-accordion-menu

一旦项目生成,用代码编辑器打开它,运行npm start ,启动开发服务器。

React中的项目架构

为了组织我们的项目,我们将把项目布局分成四个独立的单元,称为组件。

父组件,App ,持有两个直接的子组件,HeaderAccordionAccordion 持有独立的AccordionItem

FAQ Application Layout Diagram Labeling Header, Accordion, App, And AccordionItem

让我们为每个组件创建一个文件。首先,删除src 文件夹中的所有文件,并创建一个新的index.js 文件以防止分页。

接下来,在src 中创建一个名为components 的文件夹,并添加以下文件:App.js,Accordion.js,AccordionItem.js, 和Header.js

App.js 内,添加以下起始代码。

const App = () => {
  return (
    <div className="container">
      Header and Accordion here
    </div>
  )
}

export default App

注意,我们在div 容器中包含了className ,以便对元素应用CSS样式。因此,让我们在src 文件夹中创建一个app.css 文件来存放我们的应用程序的CSS样式,从这里复制CSS样式,并将它们添加到app.css 文件中。

现在,进入src/index.js 文件,像这样渲染App 组件。

import React from "react";
import ReactDOM from "react-dom";

import App from "./components/App";
// styles
import "./app.css";

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);

保存所有的文件,看到App 的内容在前端呈现。

创建手风琴式的标题组件

Header 组件只渲染了标题内容。因此,让我们简单地在components/Header.js 文件中添加以下代码。

const Header = () => {
  return (
    <h1 className="heading">
      FAQ help section 
    </h1>
  ) 
};

export default Header;

保存该文件。然后,在components/App.js 文件中导入该组件。

import Header from "./Header";

const App = () => {
  return (
    <div className="container">
      <Header />
    </div>
  );
};

export default App;

保存文件,就可以在前端看到标题文本的渲染了。

在React中创建手风琴组件

设计中的Accordion 组件持有单独的AccordionItemAccordionItem 是一个持有标题和下拉内容的组件。

为了创建标题和下拉内容,在src 文件夹中创建一个data.js 文件并添加以下内容。

export const faqs = [
  {
    question: "Lorem ipsum dolor sit amet?",
    answer:
      "Tenetur ullam rerum ad iusto possimus sequi mollitia dolore sunt quam praesentium. Tenetur ullam rerum ad iusto possimus sequi mollitia dolore sunt quam praesentium.Tenetur ullam rerum ad iusto possimus sequi mollitia dolore sunt quam praesentium.",
  },
  {
    question: "Dignissimos sequi architecto?",
    answer:
      "Aperiam ab atque incidunt dolores ullam est, earum ipsa recusandae velit cumque. Aperiam ab atque incidunt dolores ullam est, earum ipsa recusandae velit cumque.",
  },
  {
    question: "Voluptas praesentium facere?",
    answer:
      "Blanditiis aliquid adipisci quisquam reiciendis voluptates itaque.",
  },
];

在这里,我们创建了一个名为faqs 的对象数组。我们必须循环浏览这些数据,以显示单个的faqs 项目。然后,保存该文件。

在React中,数据通过props从父组件向下流动到子组件。在这种情况下,我们在Accordion 父组件中导入数据,通过它进行循环,并向下传递到AccordionItem 组件。

打开components/Accordion.js 文件,添加以下代码来导入数据。

import { faqs } from "../data";
import AccordionItem from "./AccordionItem";

const Accordion = () => {
  return (
    <ul className="accordion">
      {faqs.map((faq, index) => (
        <AccordionItem key={index} faq={faq} />
      ))}
    </ul>
  );
};

export default Accordion;

现在我们可以通过faq 道具访问AccordionItem 组件中的数据。

接下来,我们必须保存该文件,并将Accordion 导入到App.js 文件中。

// ...
import Accordion from "./Accordion";

const App = () => {
  return (
    <div className="container">
      {/* ... */}
      <Accordion />
    </div>
  );
};

// ...

components/AccordionItem.js 文件中,添加以下代码来访问questionanswer 道具的数据,以便在我们的JSX标记中使用它们。

const AccordionItem = ({ faq }) => {
  const { question, answer } = faq;
  return (
    <li className="accordion_item">
      <button className="button">
        {question}
        <span className="control"></span>
      </button>
      <div className="answer_wrapper">
        <div className="answer">{answer}</div>
      </div>
    </li>
  );
};

export default AccordionItem;

再次保存文件后,应用程序的FAQ部分现在看起来像这样。

Accordion Menu Opened Showing All Headers And Text

切换手风琴面板

让我们定义一个逻辑来检测一个被点击的AccordionItem 组件。使用一个AccordionItem 索引号,我们可以返回一个布尔值来动态地切换面板,并使用该值来设计活动面板。

要做到这一点,我们必须了解如何在React中引发和处理一个事件

当用户点击手风琴面板时,我们必须联系到管理状态的组件来更新状态值。

持有单个标题和下拉内容的AccordionItem 组件触发了一个on-click事件,而Accordion 组件(将持有状态)处理该事件。

这是提高一个从子组件到父组件的事件,为了开始这个过程,我们必须定义状态来管理点击事件。

the components/Accordion.js 文件中,让我们添加useState()

import { useState } from "react";
// ...

const Accordion = () => {

  const [clicked, setClicked] = useState("0");

  return (
    // ...
  );
};

export default Accordion;

添加了状态后,我们必须添加一个处理程序来更新点击面板时的状态值,以及一个使用状态值来动态显示或隐藏手风琴面板的逻辑。

首先,我们将在return 语句上方添加一个处理函数,handleToggle ,并将其传递给AccordionItem 组件。现在component/Accordion.js 文件看起来像这样。

// ...

const Accordion = () => {
  const [clicked, setClicked] = useState("0");

  const handleToggle = (index) => {
    if(clicked === index) {
      return setClicked("0")
    }
    setClicked(index)
  };

  return (
    <ul className="accordion">
      {faqs.map((faq, index) => (
        <AccordionItem
          onToggle={() => handleToggle(index)}
          active={clicked === index}
          // ...
        />
      ))}
    </ul>
  );
};

export default Accordion;

handleToggle 处理程序接收被点击的面板的索引并更新状态值。请注意,我们通过一个回调函数将处理程序传递给AccordionItem 组件,并添加了一个active 的道具来检测哪个面板是否处于活动状态。

现在,我们可以通过onToggle 道具访问handleToggle 处理程序,也可以通过active 道具访问AccordionItem 组件中的活动面板。

更新components/AccordionItem.js 文件以包括activeonToggle 道具。

const AccordionItem = ({ faq, active, onToggle }) => {
  const { question, answer } = faq;
  return (
    <li className={`accordion_item ${active ? "active" : ""}`}>
      <button className="button" onClick={onToggle}>
        {question}
        <span className="control">{active ? "—" : "+"} </span>
      </button>
      <div className={`answer_wrapper ${active ? "open" : ""}`}>
        <div className="answer">{answer}</div>
      </div>
    </li>
  );
};

export default AccordionItem;

随着onToggle 道具分配给button 元素的onClick 事件,点击按钮现在会触发Accordion 组件中的handleToggle 函数。

active 道具是一个布尔值,它将类名添加到活动面板上,使其具有风格。

现在我们可以更新CSS文件以适应动态类名。打开src/app.css 文件,在底部添加以下样式。

/* activate toggle */
.accordion_item.active .button {
  background-color: #105057;
}
.answer_wrapper {
  height: 0;
  overflow: hidden;
}
.answer_wrapper.open {
  height: auto;
}

保存所有的文件,测试应用程序,它应该可以工作。

实现一个过渡性的切换

为了使手风琴面板平滑地切换,我们将添加一个CSStransition 属性,在给定的时间内添加平滑过渡效果。

让我们更新一下.answer_wrapper ,以包括transition 这个CSS属性。

.answer_wrapper {
  /* ... */
  transition: height ease 0.2s;
}

如果我们保存文件并测试我们的项目,它将无法工作,除非我们将内容heightauto 改为一个明确的值,如100px

.answer_wrapper.open {
  height: 100px;
}

然而,这对动态内容来说并不实用,因为使用固定高度对内容来说可能太大或者太小。为了纠正这个问题,我们可以使用scrollHeight 来动态地设置下拉内容的高度。

使用DOMscrollHeight

DOMscrollHeight 属性允许我们根据内容的多少来衡量一个元素的高度。

在React中,我们可以通过useRef Hook访问DOM元素,以获得其内容高度。

打开AccordionItem.js 文件,导入useRef Hook,像这样。

import { useRef } from "react";

const AccordionItem = ({ faq, active, onToggle }) => {
  // ...

  const contentEl = useRef();

  return (
    <li className={`accordion_item ${active ? "active" : ""}`}>
      {/* ... */}
      <div ref={contentEl} className="answer_wrapper">
        <div className="answer">{answer}</div>
      </div>
    </li>
  );
};

// ...

注意我们如何将一个ref 道具传递给包含下拉内容的目标元素。我们也从下拉内容元素中删除了open 的类名。我们现在可以使用style 属性中的scrollHeight ,动态地添加明确的内容高度。

<div
  ref={contentEl}
  className="answer_wrapper"
  style={
    active
      ? { height: contentEl.current.scrollHeight }
      : { height: "0px" }
  }
> 
  <div className="answer">{answer}</div>
</div>

再次保存文件后,从app.css 文件中删除这个样式,因为它现在是多余的。

.answer_wrapper.open {
  height: auto;
}

现在测试该应用程序,看看切换手风琴面板时的平滑过渡效果。

总结

通过学习如何创建一个平滑过渡的手风琴菜单,你现在可以在你自己的React项目中实现它。

如果你有任何问题或贡献,请在评论区分享。你可以从这个GitHub仓库找到项目的源代码。

The postHow to build a React accordion menu from scratchappeared first onLogRocket Blog.