前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React 基础案例 | 提醒列表和旅游清单列表(一)

React 基础案例 | 提醒列表和旅游清单列表(一)

作者头像
前端达人
发布2021-07-16 17:16:38
8520
发布2021-07-16 17:16:38
举报
文章被收录于专栏:前端达人前端达人

一、开篇

大家好,本系列文章小编将和大家一起,从最基础的真实案例实践 React Hook 相关的知识,如果你已经很熟练了 React Hook 相关内容了,本系列文章你可以忽略。

本系列文章由浅入深,将从最简单的案例开始学习,本篇文章将从两个列表的数据渲染开始讲起,一个是从本地文件获取数据、另一个通过接口请求的方式获取数据。

二、案例1:生日列表加载本地数据

如下图所示,本案例从本地数据加载生日列表数据,列表数据包含了用户的头像、姓名、年龄,同时又包含了一个清除数据的按钮。

2.1、创建项目

开始之前,我们通过 create-react-app 命令创建项目 birthday-reminder,删除一些不相关的文件,保留 App.js、index.css、index.js,index.js 的文件内容修改如下:

代码语言:javascript
复制
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

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

// src/index.js

2.2、设计本地文件数据结构

本案例的数据结构比较简单,一个数组对象,包含 id、name(姓名)、age(年龄)、image(图片地址),新建 data.js 数据文件,示例结构如下:

代码语言:javascript
复制
export default [
  {
    id: 1,
    name: 'Bertie Yates',
    age: 29,
    image:
      'https://res.cloudinary.com/diqqf3eq2/image/upload/v1595959131/person-2_ipcjws.jpg',
  },
  {
    id: 2,
    name: 'Hester Hogan',
    age: 32,
    image:
      'https://res.cloudinary.com/diqqf3eq2/image/upload/v1595959131/person-3_rxtqvi.jpg',
  },
  //更多数据...
]

// src/data.js

2.3、新建本地样式文件

基于页面,我们新建 index.css 文件,定义基础的颜色和常用的尺寸变量等,由于代码比较简单,这里就不过多解释啦,直接贴上代码

代码语言:javascript
复制
/*
=============== 
Variables
===============
*/

:root {
  /* dark shades of primary color*/
  --clr-primary-1: hsl(162, 61%, 89%);
  --clr-primary-2: hsl(162, 60%, 78%);
  --clr-primary-3: hsl(162, 61%, 67%);
  --clr-primary-4: hsl(162, 61%, 57%);
  /* primary/main color */
  --clr-primary-5: hsl(162, 73%, 46%);
  /* lighter shades of primary color */
  --clr-primary-6: #1aa179;
  --clr-primary-7: #13795b;
  --clr-primary-8: #0d503c;
  --clr-primary-9: #06281e;
  /* darkest grey - used for headings */
  --clr-grey-1: hsl(212, 33%, 89%);
  --clr-grey-2: hsl(210, 31%, 80%);
  --clr-grey-3: hsl(211, 27%, 70%);
  --clr-grey-4: hsl(209, 23%, 60%);
  /* grey used for paragraphs */
  --clr-grey-5: hsl(210, 22%, 49%);
  --clr-grey-6: hsl(209, 28%, 39%);
  --clr-grey-7: hsl(209, 34%, 30%);
  --clr-grey-8: hsl(211, 39%, 23%);
  --clr-grey-9: hsl(209, 61%, 16%);
  --clr-white: #fff;
  --clr-red-dark: hsl(360, 67%, 44%);
  --clr-red-light: hsl(360, 71%, 66%);
  --clr-green-dark: hsl(125, 67%, 44%);
  --clr-green-light: hsl(125, 71%, 66%);
  --clr-black: #222;
  --transition: all 0.3s linear;
  --spacing: 0.1rem;
  --radius: 0.25rem;
  --light-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
  --dark-shadow: 0 5px 15px rgba(0, 0, 0, 0.4);
  --max-width: 1170px;
  --fixed-width: 450px;
  --clr-pink: #f28ab2;
}
/*
=============== 
Global Styles
===============
*/

*,
::after,
::before {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
    Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  background: var(--clr-pink);
  color: var(--clr-grey-9);
  line-height: 1.5;
  font-size: 0.875rem;
}
ul {
  list-style-type: none;
}
a {
  text-decoration: none;
}
h1,
h2,
h3,
h4 {
  letter-spacing: var(--spacing);
  text-transform: capitalize;
  line-height: 1.25;
  margin-bottom: 0.75rem;
}
h1 {
  font-size: 3rem;
}
h2 {
  font-size: 2rem;
}
h3 {
  font-size: 1.25rem;
}
h4 {
  font-size: 0.875rem;
}
p {
  margin-bottom: 1.25rem;
  color: var(--clr-grey-5);
}
@media screen and (min-width: 800px) {
  h1 {
    font-size: 4rem;
  }
  h2 {
    font-size: 2.5rem;
  }
  h3 {
    font-size: 1.75rem;
  }
  h4 {
    font-size: 1rem;
  }
  body {
    font-size: 1rem;
  }
  h1,
  h2,
  h3,
  h4 {
    line-height: 1;
  }
}
/*  global classes */

/* section */
.section {
  width: 90vw;
  margin: 0 auto;
  max-width: var(--max-width);
}

@media screen and (min-width: 992px) {
  .section {
    width: 95vw;
  }
}

main {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}

.container {
  width: 90vw;
  margin: 5rem 0;
  max-width: var(--fixed-width);
  background: var(--clr-white);
  border-radius: var(--radius);
  padding: 1.5rem 2rem;
  box-shadow: var(--dark-shadow);
}
.container h3 {
  font-weight: normal;
  text-transform: none;
  margin-bottom: 2rem;
}
.person {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 0.75rem;
  margin-bottom: 1.5rem;
  align-items: center;
}
.person img {
  width: 75px;
  height: 75px;
  object-fit: cover;
  border-radius: 50%;
  box-shadow: var(--light-shadow);
}
.person h4 {
  margin-bottom: 0.35rem;
}
.person p {
  margin-bottom: 0;
}
.container button {
  color: var(--clr-white);
  display: block;
  width: 100%;
  border-color: transparent;
  background: var(--clr-pink);
  margin: 2rem auto 0 auto;
  text-transform: capitalize;
  font-size: 1.2rem;
  padding: 0.5rem 0;
  letter-spacing: var(--spacing);
  border-radius: var(--radius);
  outline: 1px solid rgba(242, 138, 178, 0.8);
  cursor: pointer;
}

/*
/src/index.css
*/

2.4、设计 List 列表组件

接下来我们新建 List.js 组件,用来展示用户的列表信息,组件定义了 people 属性,用于接收 data 的数据,进行渲染列表数据。

我们使用 map 函数渲染列表数据, 同时使用 const {id,name,age,image} =person 来结构化 person的属性,示例代码如下:

代码语言:javascript
复制
import React from 'react';

const List = ({people}) => {
  return (
    <>
        {
            people.map((person)=>{
                const {id,name,age,image} =person;
                return(
                  <article key={id} className='person'>
                      <img src={image} alt={name}/>
                      <div>
                          <h4>{name}</h4>
                          <p>{age} 岁</p>
                      </div>
                  </article>
                );
            })
        }
    </>
  );
};

export default List;

// src/List.js

2.5、加载数据,渲染 LiST 列表数据

最后我们需要在 App.js 文件里,加载 data.js 中的数据,这里我们使用 state hook 函数加载 data.js 文件中的数据,定义 people 数据状态变量接收 data 数据,将其传至 List 列表中的 people 属性中渲染列表数据。最后我们添加清除按钮,使用 setPeople([]) 方法,将列表的数据清空,界面将会重新 re-render,示例代码如下:

代码语言:javascript
复制
import React, { useState } from 'react';
import data from './data';
import List from './List';
function App() {
  const [people,setPeople] = useState(data);
  return (
      <main>
        <section className='container'>
          <h3>今日 {people.length} 人过生</h3>
          <List people={people}/>
          <button onClick={()=>setPeople([])}> 清除数据</button>
        </section>
      </main>
  );
}

export default App;

// src/app.js

点击清除按钮后的效果

到这里,本案例就完成了,是一个很基础的示例,但是列表加载数据是一个很常用的场景,值得多多练习。

三、案例2:旅游清单列表请求接口加载数据

首先描述相关需求:

  1. 此案例通过接口请求数据,加载过程中,显示 Loading 状态,加载完成后显示旅游相关的图片、文章的标题、文章的描述、价格;
  2. 文字描述过长时,则会自动省略,点击 Read More 查看完整的介绍;
  3. 如果用户不敢兴趣的话,可以点击 not interested 按钮进行移除;
  4. 如果列表内容都被移除,显示 refresh 刷新按钮,点击后,重新加载旅游清单数据。

具体交互,如下视频所示:

3.1、 创建项目

开始之前,我们通过 create-react-app 命令创建项目 tours,删除一些不相关的文件,保留 App.js、index.css、index.js,index.js 文件内容如下:

代码语言:javascript
复制
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

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

// src/index.js

3.2、设计接口对象数据

基于界面展示的需求,我们的接口数据需要返回一个数组对象,包含:id(主键)、name(标题)、info(信息)、image(图片)、price(价格),本篇示例,提供一个 https://course-api.com/react-tours-project 接口地址,返回如下数据:

3.3、创建 index.css 样式

基于界面样式,我们新建 index.css 样式文件,由于代码比较简单,这里只是贴上相关代码,代码如下所示:

代码语言:javascript
复制
/*
=============== 
Variables
===============
*/

:root {
  /* dark shades of primary color*/
  --clr-primary-1: hsl(205, 86%, 17%);
  --clr-primary-2: hsl(205, 77%, 27%);
  --clr-primary-3: hsl(205, 72%, 37%);
  --clr-primary-4: hsl(205, 63%, 48%);
  /* primary/main color */
  --clr-primary-5: hsl(205, 78%, 60%);
  /* lighter shades of primary color */
  --clr-primary-6: hsl(205, 89%, 70%);
  --clr-primary-7: hsl(205, 90%, 76%);
  --clr-primary-8: hsl(205, 86%, 81%);
  --clr-primary-9: hsl(205, 90%, 88%);
  --clr-primary-10: hsl(205, 100%, 96%);
  /* darkest grey - used for headings */
  --clr-grey-1: hsl(209, 61%, 16%);
  --clr-grey-2: hsl(211, 39%, 23%);
  --clr-grey-3: hsl(209, 34%, 30%);
  --clr-grey-4: hsl(209, 28%, 39%);
  /* grey used for paragraphs */
  --clr-grey-5: hsl(210, 22%, 49%);
  --clr-grey-6: hsl(209, 23%, 60%);
  --clr-grey-7: hsl(211, 27%, 70%);
  --clr-grey-8: hsl(210, 31%, 80%);
  --clr-grey-9: hsl(212, 33%, 89%);
  --clr-grey-10: hsl(210, 36%, 96%);
  --clr-white: #fff;
  --clr-red-dark: hsl(360, 67%, 44%);
  --clr-red-light: hsl(360, 71%, 66%);
  --clr-green-dark: hsl(125, 67%, 44%);
  --clr-green-light: hsl(125, 71%, 66%);
  --clr-black: #222;
  --transition: all 0.3s linear;
  --spacing: 0.1rem;
  --radius: 0.25rem;
  --light-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
  --dark-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
  --max-width: 1170px;
  --fixed-width: 620px;
}
/*
=============== 
Global Styles
===============
*/

*,
::after,
::before {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
    Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  background: var(--clr-grey-10);
  color: var(--clr-grey-1);
  line-height: 1.5;
  font-size: 0.875rem;
}
ul {
  list-style-type: none;
}
a {
  text-decoration: none;
}
h1,
h2,
h3,
h4 {
  letter-spacing: var(--spacing);
  text-transform: capitalize;
  line-height: 1.25;
  margin-bottom: 0.75rem;
}
h1 {
  font-size: 3rem;
}
h2 {
  font-size: 2rem;
}
h3 {
  font-size: 1.25rem;
}
h4 {
  font-size: 0.875rem;
}
p {
  margin-bottom: 1.25rem;
  color: var(--clr-grey-5);
}
@media screen and (min-width: 800px) {
  h1 {
    font-size: 4rem;
  }
  h2 {
    font-size: 2.5rem;
  }
  h3 {
    font-size: 1.75rem;
  }
  h4 {
    font-size: 1rem;
  }
  body {
    font-size: 1rem;
  }
  h1,
  h2,
  h3,
  h4 {
    line-height: 1;
  }
}
/*  global classes */

/* section */
.section {
  width: 90vw;
  margin: 0 auto;
  max-width: var(--max-width);
}

@media screen and (min-width: 992px) {
  .section {
    width: 95vw;
  }
}
.btn {
  background: var(--clr-primary-5);
  display: inline-block;
  padding: 0.25rem 0.5rem;
  border-radius: var(--radius);
  text-transform: capitalize;
  color: var(--clr-white);
  letter-spacing: var(--spacing);
  border-color: transparent;
  cursor: pointer;
  margin-top: 2rem;
  font-size: 1.2rem;
}
/*
=============== 
Tours
===============
*/
main {
  width: 90vw;
  max-width: var(--fixed-width);
  margin: 5rem auto;
}
.loading {
  text-align: center;
}
.title {
  text-align: center;
  margin-bottom: 4rem;
}
.underline {
  width: 6rem;
  height: 0.25rem;
  background: var(--clr-primary-5);
  margin-left: auto;
  margin-right: auto;
}

.single-tour {
  background: var(--clr-white);
  border-radius: var(--radius);
  margin: 2rem 0;
  box-shadow: var(--light-shadow);
  transition: var(--transition);
}
.single-tour:hover {
  box-shadow: var(--dark-shadow);
}
.single-tour img {
  width: 100%;
  height: 20rem;
  object-fit: cover;
  border-top-right-radius: var(--radius);
  border-top-left-radius: var(--radius);
}
.tour-info {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.5rem;
}
.tour-info h4 {
  margin-bottom: 0;
}
.single-tour button {
  background: transparent;
  border-color: transparent;
  text-transform: capitalize;
  color: var(--clr-primary-5);
  font-size: 1rem;
  cursor: pointer;
  padding-left: 0.25rem;
}
.tour-price {
  color: var(--clr-primary-5);
  background: var(--clr-primary-10);
  padding: 0.25rem 0.5rem;
  border-radius: var(--radius);
}
.single-tour footer {
  padding: 1.5rem 2rem;
}
.single-tour .delete-btn {
  display: block;
  width: 200px;
  margin: 1rem auto 0 auto;
  color: var(--clr-red-dark);
  letter-spacing: var(--spacing);
  background: transparent;
  border: 1px solid var(--clr-red-dark);
  padding: 0.25rem 0.5rem;
  border-radius: var(--radius);
}

/*
  src/index.css
*/

3.4、创建加载 Loading 组件

在数据请求阶段,我们需要给用户一个数据正在加载中的状态提示,此时我们需要新建一个 Loading 的组件,代码比较简单,新建一个 Loading.js 的文件,示例代码如下:

代码语言:javascript
复制
import React from 'react';

const Loading = () => {
  return (
    <div className="loading">
      <h1>loading...</h1>
    </div>
  );
};

export default Loading;

// src/Loading.js

3.5、创建清单 Tour 卡片组件

由于清单列表中单个卡片的内容比较多,比如清单的图片、标题、描述信息展示以及 readMore 操作按钮查看完整的信息描述、点击 not interested 移除卡片清单,因此我们要单独创建 Tour.js 卡片组件。

这里我们为组件定义 id,image(图片),info(信息),name(标题),price(价格),removeTour(移除事件属性),同时我们定义 readMore 状态(state hook)用来显示或省略详情内容。

代码语言:javascript
复制
import React, { useState } from 'react';

const Tour = ({id,image,info,name,price,removeTour}) => {
  const [readMore,setReadMore] = useState(false);
  return (
      <article className="single-tour">
        <img src={image} alt={name}/>
        <footer>
          <div className="tour-info">
            <h4>{name}</h4>
            <h4 className="tour-price">${price}</h4>
          </div>
          <p>
            {readMore ? info:`${info.substring(0,200)}...`}
            <button onClick={()=>setReadMore(!readMore)}>
                {readMore?'show less':'read more'}
            </button>
          </p>
          <button className='delete-btn' onClick={()=>removeTour(id)}>
              not interested
          </button>
        </footer>
      </article>
  );
};

export default Tour;

// src/Tour.js

3.6、创建 Tours 列表组件

接下来我们创建卡片清单列表,新建 Tours.js 文件,列表组件定义 tours, removeTour 两个属性,tours 属性用于接收父组件传递的数据,removeTour 事件用于删除对应的清单。列表组件通过数组的 map 函数迭代 Tour 卡片组件,渲染父组件传过来的 Data 属性,示例代码如下:

代码语言:javascript
复制
import React from 'react';
import Tour from './Tour';
const Tours = ({tours, removeTour}) => {
  return (
      <section>
        <div className="title">
          <h2>our tours</h2>
          <div className="underline"></div>
        </div>
        <div>
          {tours.map((tour)=>{
            return <Tour key={tour.id} {...tour} removeTour={removeTour} />;
          })}
        </div>
      </section>
  );
};

export default Tours;
// src/Tours.js

3.7、完善 App.js 界面

最后我们完善 App.js 界面,引入 Tours 组件,按照以下思路编写:

  • 定义接口地址 URL 变量,https://course-api.com/react-tours-project
  • 定义加载数据状态和清单数据状态(state hook):loading 和 tours,用来显示加载状态和渲染接口的数据
  • 定义 removeTour 事件,使用 filter 属性删除对应清单。
  • 接下来定义接口请求方法 fetchTours,使用 async,await 方式请求接口,通过 useEffect Hook 调用 fetchTours 方法,最后别忘记 useEffect 的第二个参数 [] 为空数组,只加载一次;
  • 最后使用条件语句,判断数据是否加载中,显示 Loading 组件;接口请求完成时,调用 Tours 组件,显示清单列表;如果清单列表为空,显示 refresh 重新加载数据的按钮,点击时,重新请求接口加载数据;

基于以上的思路,完成后的代码如下所示:

代码语言:javascript
复制
import React, { useState, useEffect } from 'react'
import Loading from './Loading'
import Tours from './Tours'
// ATTENTION!!!!!!!!!!
// I SWITCHED TO PERMANENT DOMAIN
const url = 'https://course-api.com/react-tours-project'
function App() {
  const [loading,setLoading]=useState(true);
  const [tours,setTours]=useState([]);

  const removeTour = (id)=>{
    const newTours = tours.filter((tour)=> tour.id !==id);
    setTours(newTours);
  }

  const fetchTours = async ()=>{
    setLoading(true);
    try {
      const response = await fetch(url);
      const tours=await response.json();
      setLoading(false);
      setTours(tours);
    } catch (error) {
      setLoading(false);
      console.log(error);
    }
  }

  useEffect(()=>{
    fetchTours()
  },[])

  if(loading){
    return (
        <main>
          <Loading/>
        </main>
    )
  }

  if(tours.length===0){
    return (
        <main>
          <div className='title'>
            <h2>no tours left</h2>
            <button className='btn' onClick={()=>fetchTours()}>
              refresh
            </button>
          </div>
        </main>
    )
  }

  return (
      <main>
        <Tours tours={tours} removeTour={removeTour}/>
      </main>
  )
}

export default App

到这里,本案例相关所有的代码就完成了,是不是很简单呢。

结束语

今天的两个实例就介绍这里,虽然简单,但是在实际应用场景又很常见,大家还是有必要亲自动手练习的

相关阅读

React Hooks 学习笔记 | State Hook(一)

React Hooks 学习笔记 | useEffect Hook(二)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-07-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端达人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、开篇
  • 二、案例1:生日列表加载本地数据
    • 2.1、创建项目
      • 2.2、设计本地文件数据结构
        • 2.3、新建本地样式文件
          • 2.4、设计 List 列表组件
            • 2.5、加载数据,渲染 LiST 列表数据
            • 三、案例2:旅游清单列表请求接口加载数据
              • 3.1、 创建项目
                • 3.2、设计接口对象数据
                  • 3.3、创建 index.css 样式
                    • 3.4、创建加载 Loading 组件
                      • 3.5、创建清单 Tour 卡片组件
                        • 3.6、创建 Tours 列表组件
                          • 3.7、完善 App.js 界面
                          • 结束语
                          • 相关阅读
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档