还在烦恼没有项目?手把手带你从 0 开始用 React 重写学成在线 II

  • 还在烦恼没有项目?手把手带你从 0 开始用 React 重写学成在线 II
    • 前情回顾
    • 补充 Header 中的细节
    • 继续实现首页的逻辑
      • 首页的 banner
        • 背景
        • 轮播图
        • 轮播图上方的课程模块
          • 专业方向列表
          • 我的课程
          • 整合
      • 精品推荐
    • 源码
    • 总结

还在烦恼没有项目?手把手带你从 0 开始用 React 重写学成在线 II

看完这篇教程,你应该就能掌握了以下知识点:

  • 知道 react-router-dom 的 useLocation 这个钩子函数
  • 在 React 中动态添加类名
  • 知道父子组件之间怎么信息
  • 使用依赖包实现轮播图效果
  • 利用绝对定位解决元素叠加问题

前情回顾

上一篇 [万字长文]使用 React 重写学成在线前端项目 I 代码完整可运行,步骤有详解 中,最后渲染的结果是这样的:

既然大体的框架都已经完成的差不多了,那么现在就继续往里面填充一点细节吧。

补充 Header 中的细节

Header 中的悬浮效果已经做好了,但是对当前所在的 url 没有任何的提示。对网站不熟悉的用户而言,会让用户无法快速识别自己所在的页面,导致不好的用户体验。

因此,下面针对这个地方进行一下优化。

要做这个判断也很简单,react-router-dom 封装好了一个名为 useLocation 的函数,可以用来判断当前所在的 url。现在,导入 useLocation 这个函数,并且在循环中判断当前的 url 与子项中的 url 是否一致,如果一致,则证明该子项就是当前所在页面。

这个判断可以通过三元表达完成,证明当前子项是所在页面之后,可以添加一个新的类名去实现高亮效果。

这样就实现了 React 之中动态添加添加类名的功能。


关于三元表达式,也就是 val = condition ? a : b 的语法,它的意思是当 condition 是 true 的时候,val 的值是 a,不然 val 的值是 b,与下面的 if else 的表达式是一样的:

if (condition === true) {val = a;
} else {val = b;
}// 等同于
val = condition ? a : b;

在简单的逻辑判断中,是非常常用的方法。


这一部分的代码写完之后,效果是这样的:

已经可以看到导航栏下方会出现当前页面的指示器,并且该指示器会随着页面的变动而变动。

下面为修改过的代码:

JavaScript:

import React from 'react';
import NAV_LINKS from '../../../constants/navLinks';
import { Link } from 'react-router-dom';
// =================新增的代码===============
import { useLocation } from 'react-router-dom';
// =================新增的代码===============const Nav = () => {// =================新增的代码===============const location = useLocation();// =================新增的代码===============return (<nav className="nav"><ul className="flex">{NAV_LINKS.map((link) => (<likey={link.name}// =================新增的代码===============// 如果当前url等于nav中的url,则证明这是当前页面className={location.pathname === link.url ? 'active-nav' : null}// =================新增的代码===============><Link to={link.url}>{link.name}</Link></li>))}</ul></nav>);
};export default Nav;

CSS:

.nav li {margin-left: 65px;/* 下面为修改过的代码 */padding: 8px;/* 上面为修改过的代码 */
}
/* 下面为新增的代码 */
.active-nav {border-bottom: 2px solid #00a4ff;
}
/* 上面为新增的代码 */

此时,又出现了一个小问题,那就是如果定向到了具体的某一门课的时候,那么导航栏的指示器会再一次失去效果:

这是因为导航栏里面只有 /, /courses, 和 career-path 的规划,没有办法正确匹配到 /courses/:id 的路径。

这里就偷懒且粗暴的做简单的字符串匹配就好了,只要判断当前 url 里面有 /courses,并且遍历的子项中也有 /courses,就算二者匹配了。

这种复杂的逻辑用三元表达写也比较吃力,所以就单独抽出来做了一个函数:

// 忽略一些引用
import * as routePaths from '../../../constants/routerPaths';const Nav = () => {const location = useLocation();// =================新增的代码===============const isActiveNav = (pathname, url) => {// 因为目前只考虑 courses/:id 的特殊项,所以写死了这一个判断let isActive =pathname === url || // 当path正好与url相等(pathname.includes(routePaths.COURSES) && // 在 /coursesurl.includes(routePaths.COURSES));return isActive;};// =================新增的代码===============return (<nav className="nav"><ul className="flex">{NAV_LINKS.map((link) => {const { url } = link;return (<likey={link.name}// =================修改的代码===============className={isActiveNav(location.pathname, url) ? 'active-nav' : null}// =================修改的代码===============><Link to={url}>{link.name}</Link></li>);})}</ul></nav>);
};export default Nav;

这样,导航栏的优化就已经实现了。

继续实现首页的逻辑

现在就继续实现首页的逻辑,在这一个部分,主要将会实现一下两个模块:

  • 首页的 banner

    PSD 中的效果图是这样的:

  • 精品推荐

    PSD 中的效果图是这样的:

首页的 banner

本来这一块我以为是可以单独抽出来做一个 banner 组件的,但是后来发现,这么做的意义不是特别大。

另一个有 banner 的页面在职业规划中,效果图如下

可以看出来,二者的特性在于,看起来都有一个背景图,并且背景图片的宽度都是 100%,这也是我本来想封装的原因。再具体实现的过程中却发现,二者还是有一些不同的:

  • 高度不同,所以还是得另外写 CSS
  • 首页的 banner 中间有一个轮播图,
  • 这里其实这里使用的不是背景图,而是背景颜色

鉴于不同之处多过相似之处,强行封装未免有种过度封装的感觉,所以原本决定使用的 banner 组件放弃,直接在首页写轮播图。

接下来对首页的 banner 进行业务需求的分析,可以得出,这里可以拆分出 3 个部分——部分,不是模块——分别为:

  • 背景

    只是背景颜色,很好做

  • 轮播图

    需要让图片动起来的轮播图

  • 轮播图上方的课程模块

    处于轮播图上方的课程模块,再细分还能分成:

    • 推荐领域
    • 我的课程表

因此,这里决定新增了 4 个模块去实现对应的功能。

结构如下:

结构为:

|- home
|  |-  homeCarousel
|  |  |- courseContent
|  |  |  |- courseSubNav
|  |  |  |- myCourseList
|  |  |- index.js
|- Home.js

具体实现代码之前,现将基础结构修改一下。

主要就是修改一下 Component 这个目录中,Home.js 之中的类名,删掉 Home 这个 placeholder,开始往里面填充内容,同时更新一下对应的 CSS。

import React from 'react';const Home = () => {return <div className="homepage"></div>;
};export default Home;

背景

只要修改一下 CSS 就好了:

CSS:

.homepage-banner {height: 420px;background-color: #1c036c;
}

轮播图

这里使用的是一个叫做 react-responsive-carousel 依赖包去完成的轮播图,安装插件的方法是在中断输入下面的命令:

npm install yarn add react-responsive-carousel

等安装完成了,就可以在项目之中使用这个依赖包了。

介绍中的使用方法是这样的:

import "react-responsive-carousel/lib/styles/carousel.min.css"; // requires a loader
import { Carousel } from 'react-responsive-carousel';class DemoCarousel extends Component {render() {return (<Carousel><div><img src="assets/1.jpeg" /><p className="legend">Legend 1</p></div>{/*  重复上面的 div */}</Carousel>);}
});

也就是说,在 <Carousel> </Carousel> 中放入图片就会被自动读取,而它可以接受的属性也用很多,这里挑用得到的讲一下:

  • showStatus: false

    右上角显示当前图片进度的小图,在 PSD 中没有这一块内容,所以改为 false

  • showArrows: false,

    轮播图两边的箭头,PSD 中也没有,所以改为 false

  • autoPlay: true,

    开启自动轮播

  • showThumbs: false,

    展示轮播图状态的小图,默认开启

  • infiniteLoop: true,

    是否无限循环,即到轮播图最后一张图片展示完毕后,从第一张图片重新开始

上面的配置会作为属性传到 Carousel 组件中去。

轮播图完整的代码为:

import React from 'react';
import banner from '../../../asset/img/home/banner.jpg';
import 'react-responsive-carousel/lib/styles/carousel.min.css'; // requires a loader
import { Carousel } from 'react-responsive-carousel';
import CourseContent from './courseContent';const HomeBanner = () => {// 将作为 props 传给 Carousel 这个组件const settings = {showStatus: false,showArrows: false,autoPlay: true,showThumbs: false,infiniteLoop: true,};// 手动生成一个由图片组成的数组const getBanners = () => {const banners = [];for (let i = 0; i < 5; i++) {banners.push(<div key={i}><img src={banner} alt="home-banners" /></div>);}return banners;};return (<div className="homepage-banner"><div className="container"><Carousel {...settings}>{getBanners()}</Carousel></div></div>);
};export default HomeBanner;

效果图如下:


React 的数据是只能由一个方向传输的,也就是父->子的关系。

关于父子组件传递数据,有两种方法:

  1. 数据被组件名所包裹,如上文的 Carousel 所用的那样。这种情况下,必须要有完整的开始标签和闭合标签,才可以正确的读取数据:

    <Carousel><div>{/* 这里的 div 标签就被作为 props 传递到了子组件中 */}<img src="assets/1.jpeg" /><p className="legend">Legend 1</p></div>{/*  重复上面的 div */}
    </Carousel>
    
  2. 在组件名中直接传递属性,这种方法可以用于自闭合标签,或是普通标签。这种写法相对而言是比较方便和简单的写法,也是主流的写法。

    <Carousel {...settings}>  // 这里 settings 就被展开并且作为属性传递到了子组件中
    </Carousel>// 自闭合标签的用法
    <input type='text' {...props} />
    

轮播图上方的课程模块

先新建一个父组件,名为 CourseContent,再分成两块,一块是左边的专业方向列表,另外一块是右边的我的课程表。

再重申一遍模块化的概念,模块化是将一个功能单独分割出来以达可以复用或是逻辑清晰的目的。目前来说很难有一个官方既定的标准说你应该怎么怎么分割这个模块。通用的理解就是按照功能分割。

自然这也有可能会造成过渡分割的问题,从而造成文件结构深层嵌套,难以理解。

具体怎么实现只能说怎么选择是仁者见仁,智者见智了,目前真的很难做到统一化。

这里在具体实现的时候产生了一个问题:

内容被挤到了下方,而不是叠加在轮播图上。

这是因为轮播图本身是需要占据空间的,课程模块同样需要空间。这时候使用 position: absolute; 定位即可,这样课程列表就会从文档流中脱离出来,不占用文档流中的空间。

另外,从文档流中脱离出来的内容是没有宽度的,需要使用 width: inherit; 让它继承父元素的宽度。

专业方向列表

实现后的效果图:

可以看出这个逻辑还是比较简单的,主要是使用 ul > li > a 这样嵌套的结构。

职业方向是没有办法排列的,所以这里用无序列表会比较合适。

我将具体的课程抽出来做了放入了 constants 这个文件夹之中,模拟从另一个地方接收到数据。

在开发中,这些数据其实很难会被写死,大多数情况下数据会从一些 CMS(Content Management System,内容管理系统) 中传来。为的就是当有一些小细节要变动的时候,只需要修改 CMS 中的内容,而不需要修改结构,减少开发和维护的成本。

JavaScript 代码:

import React from 'react';
import { courseSubNavList } from '../../../../../constants/home';const CourseSubNav = () => {return (<div className="course-sub-nav"><ul>{courseSubNavList.map((val) => (<li className="relative" key={val}><a href=".#">{val}</a></li>))};</ul></div>);
};export default CourseSubNav;

当然,CSS 方面要修改亿点点细节,这个可以看 index.css 和 Home.css,我主要对悬浮的特效进行了 CSS 的封装,这样大部分的 a 标签和 li 标签在悬浮的时候都会产生字体颜色上的变化。

对 a 标签进行 CSS 的封装可以很好地达到复用性,之前 header 中的导航栏也使用的是同样的 CSS,以及之后很多其他地方,也会用到同样的 当鼠标悬浮时,字体会变色 这样一个特性。

我的课程

这里的实现效果也不是很难,我最终选择了 div > dl + button 的结构。

首先是因为课程表本身就比较符合 dl > dt + dd 的结构,我的课程表 是 dl 中的标题,具体的课程是 dl 中的数据。

另外我发现这一块和下载 app 的结构很相似,所以我使用了 button 这个元素,并且对其进行了 CSS 样式的封装。

效果如下:

JavaScript 代码:

import React from 'react';
import { courseSchedule } from '../../../../../constants/home';const MyCourseList = () => {const getCourses = () => {return courseSchedule.map((course, index) => (<dd key={course.name}><a href=".#" className={index === 2 ? 'active' : null}><h4>继续学习 {course.name}</h4><p>正在学习-{course.progress}</p></a></dd>));};return (<div className="my-course-list"><dl className="my-course-list-details"><dt>我的课程表</dt>{getCourses()}</dl><button type="button" className="my-course-list-button">全部课程</button></div>);
};export default MyCourseList;
整合

先看一下效果:

再回顾一下 home 这个组件的结构:

结构为:

|- home
|  |-  homeCarousel
|  |  |- courseContent
|  |  |  |- courseSubNav
|  |  |  |- myCourseList
|  |  |- index.js
|- Home.js

现在要整合的是 courseContent 下的 index.js 以及 homeCarousel 下的 index.js

  • courseContent/index.js

    import React from 'react';
    import CourseSubNav from './courseSubNav';
    import MyCourseList from './myCourseList';const CourseContent = () => {return (<div className="absolute course-content flex space-between"><CourseSubNav /><MyCourseList /></div>);
    };export default CourseContent;
    
  • homeCarousel/index.js

    import React from 'react';
    import banner from '../../../asset/img/home/banner.jpg';
    import 'react-responsive-carousel/lib/styles/carousel.min.css'; // requires a loader
    import { Carousel } from 'react-responsive-carousel';
    import CourseContent from './courseContent';const HomeBanner = () => {// 将作为 props 传给 Carousel 这个组件const settings = {showStatus: false,showArrows: false,autoPlay: true,showThumbs: false,infiniteLoop: true,};// 手动生成一个由图片组成的数组const getBanners = () => {const banners = [];for (let i = 0; i < 5; i++) {banners.push(<div key={i}><img src={banner} alt="home-banners" /></div>);}return banners;};return (<div className="homepage-banner"><div className="container"><Carousel {...settings}>{getBanners()}</Carousel><CourseContent /></div></div>);
    };export default HomeBanner;
    

精品推荐

就是这个部分:

最终采取的还是 div > dl + div 的结构,原因和课程表的一样。

这个部分最终没有单独整合成一个模块,而是放在了一个函数里面直接渲染。

倒是这里对 CSS 进行了逻辑的抽出,这样之后在另一个页面也可以重用 CSS。

import React from 'react';
import HomeBanner from './homeCarousel';
import './Home.css';
import { fieldSuggested } from '../../constants/home';const Home = () => {const getFieldSuggestion = () => {return (<div className="card flex space-between"><dl className="flex"><dt>{fieldSuggested.title}</dt>{fieldSuggested.suggestedFields.map((field) => (<dd key={field}><a href="/#">{field}</a></dd>))}</dl><div className="modify-interested-field"><a href=".#">修改兴趣</a></div></div>);};return (<div className="homepage relative"><HomeBanner /><div className="container">{getFieldSuggestion()}</div></div>);
};export default Home;

源码

有人和我说直接贴源码的,有点不太好跟逻辑,我想了下,因为我已经习惯了这种写法可能还好,对于不习惯这种写法的,可能会存在这个问题。

所以这次我直接把源码打包放到了资源里面,可以直接下载:react 学成在线 part2

总结

这一章主要讲的内容有:

功能方面:

  • 对 Header 的优化
  • 轮播图的实现(使用别人写好依赖包)
  • 实现了首页的 banner 组件
  • 利用绝对定位解决元素叠加问题

以及头部提到的一些 React 知识点的学习:

  • 知道 react-router-dom 的 useLocation 这个钩子函数
  • 在 React 中动态添加类名
  • 知道父子组件之间怎么信息

还在烦恼没有项目?手把手带你从 0 开始用 React 重写学成在线 II相关推荐

  1. [万字长文]使用 React 重写学成在线前端项目 I 代码完整可运行,步骤有详解

    [万字长文]使用 React 重写学成在线前端项目 I 代码完整可运行,步骤有详解 准备工作 安装必备工具/库 nodejs React 脚手架 需要的 node 依赖包 分析需求 初始化项目 搭建框 ...

  2. 学成在线项目-轮播图banner

    学成在线项目-轮播图banner 1.效果图如下 2.html代码如下: <!DOCTYPE html> <html lang="en"> <head ...

  3. 手把手带你从0完成医疗行业影像图像检测三大经典模型InceptionV3-RestNet50-VGG16(附python源代码及数据库)——改变世界经典人工智能项目实战(一)手把手教学迁移学习

    手把手带你从0完成医疗行业影像图像检测三大经典模型InceptionV3-RestNet50-VGG16 1.迁移学习简介 2.项目简介 3.糖尿病视网膜病变数据集 4.考虑类别不平衡问题 5.定义模 ...

  4. 微服务实战项目-学成在线-课程发布模块

    学成在线-课程发布模块 1 模块需求分析 1.1 模块介绍 课程信息编辑完毕即可发布课程,发布课程相当于一个确认操作,课程发布后学习者在网站可以搜索到课程,然后查看课程的详细信息,进一步选课.支付.在 ...

  5. 学成在线项目开发技巧整理---第一部分

    学成在线项目开发技巧整理---第一部分 1.数据字典 2.http-client远程测试插件,可以保存测试数据 3.三种跨域解决 4.具有多层级数据查询思路 5.Mybaits分页插件原理 6.根据文 ...

  6. 黑马学成在线--项目环境搭建

    完整版请移步至我的个人博客查看:https://cyborg2077.github.io/ 学成在线–项目环境搭建 学成在线–内容管理模块 学成在线–媒资管理模块 学成在线–课程发布模块 学成在线–认 ...

  7. 2023年最新黑马程序员Java微服务项目--学成在线

    正式上线Java微服务项目<学成在线> 项目对程序员的重要性 不用播妞多说了吧 更重要的是 这次是完整!实战!企业级!项目! 划重点:全新发布!正式上线! <学成在线>项目以在 ...

  8. Java开发实战项目分享之学成在线v1.0项目总结

    前言: 学成在线项目是传智燕青老师研发的JavaEE分布式微服务架构项目,采用SpringCloud框架研发,课程共20天,应广大学员的建议现将整个项目的技术点以问题的形式进行总结,方便大家学习总结. ...

  9. 【前端】学成在线网页项目练习制作

    目录 html代码 css代码 Hello我是Leo,最近开始学习前端,学习的差不多了开始做简单的项目. 这是我学习前端路上的一个练习,跟着billbill黑马程序员pink老师做的,语法有不规范的地 ...

  10. 学成在线--0.项目概述

    文章目录 0.前言 1.功能架构 2.技术架构 3.技术栈 4.开发步骤 0.前言 当前市场的在线教育模式多种多样,包括:B2C.C2C.B2B2C等业务模式,学成在线采用B2B2C业务模式,即向 企 ...

最新文章

  1. mSystems:青大苏晓泉阐述微生物组的Beta多样性-从全局比对到局部比对
  2. python爬虫百科-Python爬虫之requests库介绍(一)
  3. delphi 到出execl2010 文件损坏_Win7系统出现explorer.exe损坏的图像的解决方法是什么?...
  4. 2013长春区域赛总结
  5. 湖南计算机专业专科排名2015,湖南最好的公办专科大学有哪些排名揭秘?湖南十大专科学校推荐?...
  6. 中心城镇问题(长链剖分优化树形dp)
  7. 机械硬盘 运行 linux 很慢,如果读写硬盘操作有问题,假死机、很慢等,就检查一下硬盘坏道...
  8. 使用微信JSSDK自定义微信分享标题、描述、和图标
  9. 本页不但包含安全的内容,也包含不安全的内容。是否显示不安全的内容
  10. c# 获取路径的盘符_C# IO操作之 如何玩转路径
  11. 「长乐集训 2017 Day1」区间 线段树
  12. 基于Android技术的物联网应用开发
  13. LeetCode 661. Image Smoother
  14. 专升本英语——语法知识——高频语法——第五节 状语从句(时间状语从句-地点状语从句-原因状语从句-结果状语从句-条件状语从句-目的状语从句-让步状语从句-比较状语从句-方式状语从句)【学习笔记】
  15. Excel,根据一列的子集进行筛选
  16. linux涂鸦软件,绘图应用程序:Pinta,Krita,Tux Paint,Drawpile,MyPaint,KolourPaint
  17. spss进行多元线性回归并分析表格(转载)
  18. 文件加密解密大师 v1.64 绿色
  19. Base-N 算法加密解密实现:
  20. flash download failed 问题解决

热门文章

  1. sql获取服务器系统时间,SQL Server取系统当前时间
  2. Java权限管理(授权与认证)
  3. Python 快速入门实战教程
  4. 2017c语言国二试题,国家计算机c语言二级考试试题
  5. C语言程序每个语句都必须有行号,2012年计算机二级C语言程序设计基本概念考点归纳...
  6. php文字音频插件下载安装,Goodhertz音频插件下载
  7. SQL数据库恢复挂起
  8. 被果树点名做一个Blog游戏,答案如下...接下来换我点名了~~!!!
  9. 【全套完结】模拟电子技术基础——全套实验手册及仿真工艺实习【建议保存】
  10. 计算机windows实验原理,Windows上机实验报告