大家好,本篇文章小编将和大家一起做两个简单的案例——可折叠的问题列表和按分类展示的美食菜谱。这两个案例,我们还是继续练习 useState Hook 的用法。
在前面的两篇文章里我们已经练习过:《React 基础案例 | 提醒列表和旅游清单列表(一)》和《React 基础案例 | 支持左右按钮点击查看信息的卡片组件(二)》,为什么还要继练习呢?这就好比学数学,原理公式比较简单,要熟练掌握要多刷题。useState Hook 也类似,看似简单,但是实际应用场景千变万化,要会运用才是关键,因此还是要多实践才能够真正掌握。
好了,废话不多说,这两个案例在我们的项目中比较常见,我们一起动手开始实践吧!
首先,我们先展示下可折叠的问题列表案例,如下视频所示,默认展示问题的标题,点击加号再展示问题的答案,再次点击折叠问题,只显示问题的标题。基于这个效果我们该如何实现呢?
好了基于思路,我们开始动手实践吧
2.1、 创建项目
开始之前,我们先通过 create-react-app 命令创建项目 accordion,删除一些不相关的文件,保留 App.js、index.css、index.js。
接下来我们定义本地文件的数据结构,列表数据结构很简单,我们新建一个 data.js 文件,定义一个数组对象变量 questions,数据对象包含 id,title(问题标题),info(问题详情),数据结构如下:
const questions = [
{
id: 1,
title: 'Do I have to allow the use of cookies?',
info:
'Unicorn vinyl poutine brooklyn, next level direct trade iceland. Shaman copper mug church-key coloring book, whatever poutine normcore fixie cred kickstarter post-ironic street art.',
},
...//此处省略
{
id: 5,
title: 'When do I recieve a password ordered by letter?',
info:
'Locavore franzen fashion axe live-edge neutra irony synth af tilde shabby chic man braid chillwave waistcoat copper mug messenger bag. Banjo snackwave blog, microdosing thundercats migas vaporware viral lo-fi seitan ',
},
]
export default questions
//src/data.js
我们继续定义单项问题组件 Question,新建 Question.js 文件,用于显示单个问题项,这里定义组件的 title 标题属性,info 答案详情属性,我们可以通过父组件传值的形式将内容渲染,同时我们定义了 showInfo 数据状态变量,通过更改数据状态的真假状态实现问题答案的折叠。
import React, { useState } from 'react';
import { AiOutlineMinus, AiOutlinePlus } from 'react-icons/ai';
const Question = ({ title, info }) => {
const [showInfo, setShowInfo] = useState(false);
return (
<article className='question'>
<header>
<h4>{title}</h4>
<button className='btn' onClick={() => setShowInfo(!showInfo)}>
{showInfo ? <AiOutlineMinus /> : <AiOutlinePlus />}
</button>
</header>
{showInfo && <p>{info}</p>}
</article>
);
};
export default Question;
//src/Question.js
注:这里我们用到了 react-icons 插件,用于显示“+(加号)”和“-(减号)”图标,安装命令如下 npm install react-icons --save
接下来我们继续在 App.js 完善逻辑,引入本地数据文件 data.js 和 Question 组件,定义 questions 状态变量(state hook),初始数据为 data.js 的数据,然后通过数组的 map 方法迭代,将数据渲染至 Question 组件,示例代码如下,代码比较简单就不解释了。
import React, { useState } from 'react';
import data from './data';
import SingleQuestion from './Question';
function App() {
const [questions, setQuestions] = useState(data);
return (
<main>
<div className='container'>
<h3>questions and answers about login</h3>
<section className='info'>
{questions.map((question) => {
return (
<SingleQuestion key={question.id} {...question}></SingleQuestion>
);
})}
</section>
</div>
</main>
);
}
export default App;
//src/App.js
最后,贴上组件相关的CSS的核心代码,代码比较简单,需要源码的可以查看文末的源码获取方式,这里就不解释了。
/*
...省略一些常规变量定义和基础元素定义
*/
/*
===============
Questions
===============
*/
main {
min-height: 100vh;
/* using flex because of better browser support */
display: flex;
justify-content: center;
align-items: center;
}
.container {
width: 90vw;
margin: 5rem auto;
background: var(--clr-white);
border-radius: var(--radius);
padding: 2.5rem 2rem;
max-width: var(--fixed-width);
display: grid;
gap: 1rem 2rem;
}
.container h3 {
line-height: 1.2;
font-weight: 500;
}
@media screen and (min-width: 992px) {
.container {
display: grid;
grid-template-columns: 250px 1fr;
}
}
.question {
padding: 1rem 1.5rem;
border: 2px solid var(--clr-grey-special);
margin-bottom: 1rem;
border-radius: var(--radius);
box-shadow: var(--light-shadow);
}
.question h4 {
text-transform: none;
line-height: 1.5;
}
.question p {
color: var(--clr-grey-3);
margin-bottom: 0;
margin-top: 0.5rem;
}
.question header {
display: flex;
justify-content: space-between;
align-items: center;
}
.question header h4 {
margin-bottom: 0;
}
.btn {
background: transparent;
border-color: transparent;
width: 2rem;
height: 2rem;
background: var(--clr-grey-special);
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
color: var(--clr-red-special);
cursor: pointer;
margin-left: 1rem;
align-self: center;
min-width: 2rem;
}
/*
src/index.css
*/
到这里可折叠的问题列表我们就完成了,是不是很简单呢,这个示例,会经常在我们的业务场景应用到,虽然简单,还是建议大家亲自动手试试。
接下来我们继续做一个按分类展示的美食菜谱,这个应用场景会经常在我们的业务场景运用到,比如按分类展示文章、图片等数据。案例的展示效果如下视频所示,美食分为 All(所有)、Breakfast、Lunch、Shakes 这四个分类,默认展示所有的美食数据,然后点击对应的分类展示对应的分类的美食信息,美食信息分为美食标题、美食介绍、美食图片、美食的价格。
基于这个案例的展示效果,我们如何开始下手做呢?
好了,基于需求的梳理,我们开始动手实践吧!
开始之前,我们先通过 create-react-app 命令创建项目 menu,删除一些不相关的文件,保留 App.js、index.css、index.js。
基于案例展示所示,我们每条美食信息包含美食的名称、图片、分类、价格、描述,接下来我们新建data.js 文件,定义 menu 对象数组变量,数据示例如下:
const menu = [
{
id: 1,
title: 'buttermilk pancakes',
category: 'breakfast',
price: 15.99,
img: './images/item-1.jpeg',
desc: `I'm baby woke mlkshk wolf bitters live-edge blue bottle, hammock freegan copper mug whatever cold-pressed `,
},
// 此处省略
.....
];
export default menu;
//src/data.js
接下来我们创建菜单列表组件 Menu.js 文件,用来显示分类下对应的美食数据,代码比较简单,定义了 items 属性,用来接收父组件传递的数据,渲染列表组件,代码比较简单,这里不再解释,示例代码如下:
import React from 'react';
const Menu = ({items}) => {
return (
<div className="section-center">
{items.map((menuItem)=>{
const {id,title,img,desc,price}=menuItem;
return(
<article key={id} className='menu-item'>
<img src={img} alt={title} className='photo'/>
<div className="item-info">
<header>
<h4>{title}</h4>
<h4 className="price">${price}</h4>
</header>
<p className='item-text'>{desc}</p>
</div>
</article>
)
})}
</div>
);
};
export default Menu;
// src/Menu.js
接下来我们继续新建分类组件 Categories.js 文件,这个组件定义了分类属性categories,用来接收父组件传递的数据,同时定义 filterItems 事件属性,将当前选择的分类传递给父组件。基于这个思路,完成后的代码如下所示:
import React from 'react';
import {unstable_renderSubtreeIntoContainer} from "react-dom";
const Categories = ({categories,filterItems}) => {
return (
<div className="btn-container">
{categories.map((category,index)=>{
return(
<button
type="button"
className="filter-btn"
key={index}
onClick={()=>filterItems(category)}
>
{category}
</button>
)
})}
</div>
);
};
export default Categories;
// src/Categories.js
最后我们需要修改 App.js 文件,在这里组装刚才完成的组件和本地数据,最终呈现出视频案例的效果。具体的思路如下:
基于这些思路,完成后的代码如下所示:
import React, { useState } from 'react';
import Menu from './Menu';
import Categories from './Categories';
import items from './data';
const allCategories=['all',...new Set(items.map((item)=>item.category))];
function App() {
const [menuItems,setMenuItems]=useState(items);
const [categories,setCategories]=useState(allCategories);
const filterItems = (category)=>{
if(category==='all'){
setMenuItems(items);
return;
}
const newItems=items.filter((item)=>item.category===category);
setMenuItems(newItems);
}
return (
<main>
<section className="menu section">
<div className="title">
<h2>our menu</h2>
<div className="underline"></div>
</div>
<Categories categories={categories} filterItems={filterItems}/>
<Menu items={menuItems} />
</section>
</main>
);
}
export default App;
// src/App.js
最后,贴上组件相关的CSS的核心代码,代码比较简单,需要源码的可以查看文末的源码获取方式,这里就不解释了。
/*
===============
Menu
===============
*/
.menu {
padding: 5rem 0;
}
.title {
text-align: center;
margin-bottom: 2rem;
}
.underline {
width: 5rem;
height: 0.25rem;
background: var(--clr-gold);
margin-left: auto;
margin-right: auto;
}
.btn-container {
margin-bottom: 4rem;
display: flex;
justify-content: center;
}
.filter-btn {
background: transparent;
border-color: transparent;
font-size: 1rem;
text-transform: capitalize;
margin: 0 0.5rem;
letter-spacing: 1px;
padding: 0.375rem 0.75rem;
color: var(--clr-gold);
cursor: pointer;
transition: var(--transition);
border-radius: var(--radius);
}
.filter-btn:hover {
background: var(--clr-gold);
color: var(--clr-white);
}
.section-center {
width: 90vw;
margin: 0 auto;
max-width: 1170px;
display: grid;
gap: 3rem 2rem;
justify-items: center;
}
.menu-item {
display: grid;
gap: 1rem 2rem;
max-width: 25rem;
}
.photo {
object-fit: cover;
height: 200px;
width: 100%;
border: 0.25rem solid var(--clr-gold);
border-radius: var(--radius);
display: block;
}
.item-info header {
display: flex;
justify-content: space-between;
border-bottom: 0.5px dotted var(--clr-grey-5);
}
.item-info h4 {
margin-bottom: 0.5rem;
}
.price {
color: var(--clr-gold);
}
.item-text {
margin-bottom: 0;
padding-top: 1rem;
}
@media screen and (min-width: 768px) {
.menu-item {
grid-template-columns: 225px 1fr;
gap: 0 1.25rem;
max-width: 40rem;
}
.photo {
height: 175px;
}
}
@media screen and (min-width: 1200px) {
.section-center {
width: 95vw;
grid-template-columns: 1fr 1fr;
}
.photo {
height: 150px;
}
}
到这里按分类展示的美食菜谱的案例就介绍到这里,这个案例在实际应用中更常见,建议大家亲自动手练习下。
好了,本篇文章两个案例就介绍到这里,是不是很简单很基础呢,大家可以点击阅读原文体验本文的两个案例,如果你想获取本案例源码,请关注“前端达人”公众号,回复“b3”。感谢你的阅读。