既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
看完这篇教程,你能学到以下的知识点:
- 根据业务需求拆分组件
- 对复用组件有所了解
- 子组件如何接受父组件传来的数据——上一期学的是怎么从父组件传递数据给子组件
- 能使用 useState hook 进行状态管理
- 使用 JavaScript 中的类
- 使用 useHistory 钩子函数
- 使用 history.push() 完成页面的重定向
注: useState 对应 class based component 中的状态,是一个效果非常强大的钩子函数
前情回顾
系列文的第一篇 [万字长文]使用 React 重写学成在线前端项目 I 代码完整可运行,步骤有详解 中,主要完成了关于 Header 和 Footer 的封装:
在上一篇 还在烦恼没有项目?手把手带你从 0 开始用 React 重写学成在线 II 中,实现了完成了 banner 的动态实现 和 下面的精品推荐 部分:
在插播的内容 写好了功能/项目不知道怎么展示?手把手带你白嫖 Git Pages 部署自己的项目去惊艳面试官 中讲了如何利用 GitHub 去部署自己的项目,这个项目的在线浏览地址在这里:https://goldenaarcher.com/xczx-react/#/。
到这里,整个页面已经可以说完成了 40%了,只要再模块化三四个组件,首页剩下的内容几乎都能够动态生成。
进行业务分析
在开始具体的实现之前,我们还是应该对业务进行分析,看看怎么实现才能够达到最大效果的代码复用。
首先,看看剩下还没有实现的几个模块:
- 一个精品推荐
- 一个领域推荐
- 一个领域推荐
- 一个课程推荐
- 一个课程推荐
- 一个导师推荐
可以看出,整体的结构其实很相似,都是一个 header + 内容 的表现方式,而且所有的 header 的样式基本是一样的,只是包含的内容多少而已。
拆分一下,header 的格式就是这样的:
最左边的 title + 中间的热门点击 + 最右边的查看更多
所以第一步就先将这个 header 封装出来。
内容区其实也可以被合理的分成 3 个大模块:
- 精品推荐和课程推荐
这里其实能看到,精品推荐和课程推荐中的内容区从结构上来说都是一样的——都是一排排的课程
也因此可以将这两个内容合并成同一个模块,让它们渲染给定的课程即可
对比:
精品推荐 | 课程推荐 |
---|---|
- 领域推荐
两个领域的结构也是一样的,分为:
* 左边的 banner
* 上面的 banner
* 一系列的课程推荐鉴于这一块的布局会和 精品/课程 推荐不一样,所以再单独拆分一个模块
- 导师推荐
这个是和其他的模块差异最大的,自然而然需要被分出来
这一期内容的目的就是:
- 完成 sub header 的封装
- 完成 精品推荐和课程推荐 的基础实现
内容实现
这次就只完成一个模块:
当完成了之后你就会发现,剩下的内容渲染起来是真的很快。
subHeader 的实现
我把出现的 3 个 subHeader 全都放在了一起,这么一对比,应该就能看出结构是多么的相似,并且是可以实现复用的:
毕竟,subHeader 分割下来其实有三个部分:最左边的标题,中间的选项,以及右边的查看更多。
实现基础结构
首先,新建一个组件,这个组件需要明确接收 3 个值:
- 最左的标题,必须
这里命名为 subHeaderName
- 中间的选取,可选
这里命名为 midConent
- 右侧的查看更多,可选
这里命名为 checkMore,指查看更多
代码实现:
import React from 'react';
const SubHeader = (props) => {
const { subHeaderName, midConent, checkMore } = props;
return (
<div className="flex space-between">
<div>{subHeaderName}</div>
<div>{midConent.map((val) => val)}</div>
<div>{checkMore}</div>
</div>
);
};
export default SubHeader;
什么是 props
这里的 props 是用来接收父组件传来的数据的,在上一期轮播图中讲了父组件如何向子组件传递数据,这里讲的就是子组件如何从父组件中接收数据。
父组件向子组件传递数据的方法:
const settings = {
data: 'test',
}
<Child {...settings} />
父组件传到子组件的这些属性最终会被 React 压缩到一个对象中,约定俗成的这个对象叫做 props,这也是上文使用的。
从 props 中获取数据的方法也很简单,就像从一般的对象中获取属性一样,直接使用 props.data
,或者像上文一样解构数据都可以获得:
// using props directly
alert(props.data);
// using object destruct
const { data } = props;
alert(data);
尝试渲染
加上一点点 CSS 后,做一个测试数据来看看渲染效果吧。
先从父组件中传递一些假数据进去:
const Home = () => {
return (
// 省略其他
<div className="homepage-main">
<SubHeader
subHeaderName="编程入门"
midConent={[123]}
checkMore={'true'}
/>
</div>
);
};
看起来效果还不错,现在需要设计的就是传进去的数据类型了。
设计数据类型
已知 sub header 会接受 3 个数据类型:subHeaderName
, midConent
, 和 checkMore
。
subHeaderName
是一个纯字符串,这个也是最简单的。midConent
最终的决定是由一个由 对象 组成的数组:
[
{ title: '热门', url: '/#' },
{ title: '初级', url: '/#' },
{ title: '中级', url: '/#' },
{ title: '高级', url: '/#' },
];
checkMore
则是单独的一个对象:
{
title: '查看全部',
url: '/#',
};
其实为了省事儿单独的写 <a href='/#> 一些标题 </a>
也不是不可以,嫌麻烦的也可以直接这么写。
这种就属于每个人都会不太一样的编码风格和习惯问题,对于我来说将这些常量提取出去,一旦要修改或是清理数据都会方便很多。毕竟真实的项目中,如果后台数据没有准备好的话,大多数情况下是要自己准备 伪数据,而不是等后台开发完毕之后再使用后台传来的数据进行开发。
‼️ 所以,一定要和你的后台提前沟通好传来的数据结构是什么样子的,有文档最好,没有文档 一定 要留下邮件或是微信聊天记录作为证据,以防 被甩锅。
修改实现方法,重新渲染 sub header
中间的内容区依旧会使用 ul > li > a
的经典结构,并且使用 arr.map()
去进行遍历。
import React from 'react';
const SubHeader = (props) => {
const { subHeaderName, midConent, checkMore } = props;
const getMidContent = () => {
return (
<ul className="flex">
{midConent.map((val) => (
<li key={val.title}>
<a href={val.url}>{val.title}</a>
</li>
))}
</ul>
);
};
return (
<div className="sub-header flex space-between flex-center">
<h3>{subHeaderName}</h3>
{getMidContent()}
<div>
<a href={checkMore.url}>{checkMore.title}</a>
</div>
</div>
);
};
到这一步,距离 sub header 实现完毕还差最后一步——选中高亮的实现。
以下为静态的实现效果:
使用 useState 去实现选中高亮
选中高亮的逻辑实现起来其实并不复杂,具体步骤如下:
- 需要一个变量保存当前选中的选项,默认为
热门
- 在点击事件发生时,需要替换被选中的选项
这个部分的实现就需要借助 React 自带的一个钩子函数:setState
,setState
的存在是为了替换 类组件(class-based component) 中的 状态(state) 属性。
语法为:
建议使用 ES6 中的 const
const [property, propertyHandler] = useState(defaultValue);
其中
property
为该属性的变量名propertyHandler
为事件发生时用来更新状态的函数
这个函数只是一个标识符,它不负责处理其他的逻辑,只负责更新传进去的值
useState
中传进去的为默认值- 在需要更新状态的事件函数中调用
propertyHandler
即可。
以本业务场景为例,初始值的代码如下;
const [activeTitle, setTitle] = useState(midConent[0].title);
// 点击事件
function clickHandler(e, val) {
// 防止新页面打开 and/or 重定向的发生
e.preventDefault();
// 更新状态
setTitle(val);
}
activeTitle
定义的是当前被选中,需要被设为高亮的选项setTitle
为 点击标签时设置当前高亮选项 的函数
它会接受一个值,并且直接替换 activeTitle
useState
设置的是默认值,在这个情况下也就是热门——第一个选项的标题
点击事件 clickHandler
会还给每一个 a 标签,在 a 标签被鼠标点击后就会触发这个状态,更新当前显示高亮的标签。
实现后的动态效果:
到这一步,标题的实现就完成了。
完整 JSX 代码如下:
- SubHeader
import React, { useState } from 'react';
import PropTypes from 'prop-types';
const SubHeader = (props) => {
// =================新增部分===================
const [activeTitle, setTitle] = useState(midConent[0].title);
function clickHandler(e, val) {
e.preventDefault();
setTitle(val);
}
// =================新增部分===================
const getMidContent = () => {
return (
<ul className="flex">
{midConent.map((val) => {
const { title } = val;
return (
<li key={title}>
<a
href={val.url}
onClick={(e) => clickHandler(e, title)}
className={activeTitle === title ? 'selected' : null}
>
{title}
</a>
</li>
);
})}
</ul>
);
};
};
export default SubHeader;
- Home
const Home = () => {
return (
<div className="homepage relative">
<HomeBanner />
<div className="container">
<FieldSuggestion />
<div className="homepage-main">
<SubHeader
subHeaderName="编程入门"
midConent={subHeaderOl}
checkMore={checkMore}
/>
</div>
</div>
</div>
);
};
精品推荐和课程推荐 的实现
标题已经实现了,剩下的内容就是一排排的课程:
很明显,每一个课程的结构都是一样的:
- 课程图片
- 课程标题
- 课程描述
- 新款课程(右上角的小标题)
- 爆款课程(右上角的小标题)
那么,第一步可以先将单独的 课程 封装起来,随后再循环遍历所有的课程列表,就能够获得一排排的课表了。
封装课程组件
这就是之前在 common 里就设计好的 CourseItem 组件。
根据上面列出的课程结构,所以课程组件会需要获取以下这些属性:
- id,上文没显示的内容,这里是作为一个标识符而存在的
- title, 标题,也就是课程名称
- img, 课程图片
- peopleStudying,多少人在学习,属于课程描述的内容
- courseLevel,初级中级高级,也属于课程描述的内容
- isNew 或 isHot,用或是因为 PSD 的设计中只显示了一个,不过具体的检查我偷懒了,也没做
课程主体
暂时不考虑 isNew 或 isHot,先将主体结构实现:
import React from 'react';
const CourseItem = (props) => {
const { peopleStudying, courseLevel, id, title, img, isNew, isHot } = props;
return (
<div className="course-item">
<img src={img} />
![img](https://img-blog.csdnimg.cn/img_convert/bd18406c0ed7177f0c3a47297947d1eb.png)
![img](https://img-blog.csdnimg.cn/img_convert/7b3dc541078cfab81a2b3ce5a4faaa6d.png)
![img](https://img-blog.csdnimg.cn/img_convert/d65252e18dc0f44a7ba5ffd5dac6ab53.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**
``
import React from 'react';
const CourseItem = (props) => {
const { peopleStudying, courseLevel, id, title, img, isNew, isHot } = props;
return (
<div className="course-item">
<img src={img} />
[外链图片转存中...(img-FFsW51Jy-1714982070475)]
[外链图片转存中...(img-uIHEIb5R-1714982070475)]
[外链图片转存中...(img-mUL7RhHP-1714982070476)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**