看完这篇教程,你应该可以:
原生的项目是之前使用 HTML/CSS 完成的学成在线页面,视屏展示在这里:
学成网首页 - 静态页面展示
本篇主旨就是使用 React 去重构整个静态页面,达成一样的实现效果。
另外,虽然字数有2w5,但是很大一部分是代码。
工欲善其事,必先利其器。
在开始写代码之前,请确认一下必须的工具是否安装完毕了。
nodejs 的安装还是非常简单的,直接去官网上下载对应平台的安装包即可。
安装完毕后查看 nodejs 是否安装成功:
# 查看node版本
$ node -v
$ v14.17.0
# 查看npm版本
$ npm -v
$ 6.14.13
React 官方提供的脚手架,可以直接初始化一个可以运行的 React 项目,并且不需要手动配置。对于学习项目来说,是再合适不过的工具了。
具体安装方法如下,在终端中输入下面的命令:
$ pushd D:\front\react
# 假设你想到D盘下,front文件夹中的react文件夹里去新建项目
$ npx create-react-app my-app # 会在当前目录下新建一个名为 my-app 的文件夹
$ cd my-app # 进入文件夹,里面所有的东西都已经配置好了,可以直接启动项目
$ npm start # 开始项目
这时候项目应该就能启动了,能看到一个初始化的的页面,上面会有一个不断旋转的 React Logo。
目前不会涉及数据的处理,因此只需要一个包:react-router-dom。
先在命令行按 ctrl + c
停止运行,随后输入安装依赖包。等待安装完成后,重新开启项目:
# 安装依赖包
$ npm install --save react-router-dom
# 安装完成后,重新开启项目
$ npm start
首先分析一下业务需求,根据 PSD/视频 得知,这个项目必须要有四个页面:
首页
总课程页面
渲染了所有的课程的页面
子课程页面
渲染单独一个课程的页面
职业规划
这么一来,先搭建基础的框架,新建 2 个文件夹,1 个教 components ,1 个叫 containers,其中包含 4 个文件夹,每个文件夹分别对应上面的页面。这相当于是约定俗称的一件事情,大部分的项目都会将组件结合起来的页面放入 containers 之中交由路由去渲染,components 则负责对应页面的组件。
随后,再看看有没有什么模块是可以被重复使用的。
这些页面上大部分的模块都是比较具有唯一性的,会在页面中复用,但是不会跨页面复用。这一部分的内容就放到 components 文件夹中去实现。最后再加上一个专门管理路由的文件夹。
乍一看,而会被跨页面复用的,只有下面四个模块:
所以,新建一个 common 文件夹放会被重复调用的内容,目前的项目结构就是这样的:
结构搭好了,现在就开始往里面填充内容了。
这一块的目的是先清理一下初始化的代码,并且改为当前项目所需要的的实现。
因为在这一步还没有实现 Routes 组件所以会引起报错。但是不要紧,下面马上就将 Routes 怎么实现了。
清理掉其他不打算用的部分,引入 app,也就是主程序
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')
);
引入 Routes,使得页面可以按照 url 被访问到
import './App.css';
import Routes from './router/routes';
function App() {
return (
<div className="App">
<Routes />
</div>
);
}
export default App;
先创建 4 个空的文件夹/容器,之后再开始实现具体 UI
先加一个测试用的字符串,判断路由是否成功,每一个容器下的代码,除了 div 中包含的字符串不同之外,其他结构完全一致
import React from 'react';
const Home = () => {
return <div>home</div>;
};
export default Home;
import React from 'react';
const CareerPath = () => {
return <div>career-path</div>;
};
export default CareerPath;
import React from 'react';
const Courses = () => {
return <div>courses</div>;
};
export default Courses;
course/index.js
import React from 'react';
const Course = () => {
return <div>course</div>;
};
export default Course;
加入路由,使得 url 能够与对应的页面组件进行联动。
Switch 是 react-router-dom 内部封装好的一个组件,会从被 Switch 包裹中的页面选取第一个匹配的组件进行渲染。
exact 代表的是 url 必须与当前页面传来的 url 完全一致,这时候才会导入当前页面。
对于首页和所有的课程列表页面来说,这一块是必须的。毕竟所有的 url 都是主页的分支。
例如说 CSDN 博客的 url 是 https://blog.csdn.net/
,打开某一篇文章后的 url 是 https://blog.csdn.net/articles/details/文章id
,如果不做精确配对, index 页面又在第一个的前提下,那么只能访问到首页。
所有课程的组件用 exact 的原理是一样的
接下来,开始具体的实现:
作为常量保存所有的路由地址,这一部分单独拉出来是因为通过引用的方式调用地址,以后只要修改一处地方,其他的引用就会被自动修改。预防手动修改造成的人为失误。
export const INDEX = '/';
export const CAREER_PATH = '/career-path';
export const COURSES = '/courses';
export const COURSE = '/courses/:id';
匹配路 url 与对应的组件
import {
BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import * as routePaths from './routerPaths';
import CareerPath from '../containers/careerPath';
import Home from '../containers/home';
import Courses from '../containers/courses';
import Course from '../containers/course';
const routes = () => {
return (
<Router>
<Switch>
<Route path={
routePaths.INDEX} exact component={
Home} />
<Route path={
routePaths.CAREER_PATH} component={
CareerPath} />
<Route path={
routePaths.COURSES} exact component={
Courses} />
<Route path={
routePaths.COURSE} component={
Course} />
</Switch>
</Router>
);
};
export default routes;
以上代码全都实现完毕后,就能够根据 4 个路由去访问静态页面:
common 的结构是这样的:
考虑到每个页面都会有一个 Header 和一个 Footer,所以封装了一个高阶组件出来,接收传来的 content,返回一个
header
content
footer
这样结构的组件,可以有效地减少四处复制黏贴的问题,也可以有效地减少代码量。
import React from 'react';
import Header from '../header/index';
export default function HeaderFooterHOC(WrappedComp) {
class HOC extends React.Component {
render() {
return (
<>
<Header />
<WrappedComp />
<Footer />
</>
);
}
}
return HOC;
}
在搭结构的过程中,现在只是放一个占位符而已,具体实现下一个模块
import React from 'react';
import '../../index.css';
import './header.css';
const index = () => {
return (
<div className="header flex">
<div className="container">Header Area</div>
</div>
);
};
export default index;
import React from 'react';
import '../../index.css';
import './footer.css';
const Footer = () => {
return (
<div className="footer">
<div className="container">Footer Area</div>
</div>
);
};
export default Footer;
这时候页面的基础结构就是这样的:
介于篇幅的关系,这里做的是通用组件 Header 和 Footer
填充 header 的内容,先把原本的页面结构复制黏贴下来,并且修改一下图片引用。
修改图片引用指的是,本来的图片地址是一个目录结构,如 ../../img/some-img.png
,但是因为 React 会使用 WebPack 对项目进行打包,分割图片与 JavaScript,所以这样会找不到图片的地址。
模块内引用的方法是使用 import from
语句去进行正确的导入,WebPack 会根据 import from
去寻找打包后正确的路径。
为了能够正确的引入图片,我这里在根目录下面新建了一个 asset 文件去存放图片,其结构如下
|- src
| |- asset
| | |- img
| | | |- header
| | | | |- 一干图片文件
初步修改后的代码如下:
import React from 'react';
import '../../index.css';
import './header.css';
import pic from '../../asset/img/header/logo.png';
import faSearch from '../../asset/img/header/fa-search.png';
import ld from '../../asset/img/header/ld.png';
import profile from '../../asset/img/header/pic.png';
const index = () => {
return (
<div className="header flex">
<div className="logo">
<img src={
pic} alt="logo" />
</div>
<div className="container flex">
<ul class="menu flex">
<li class="homepage active">
<a href="./index.html">首页</a>
</li>
<li class="courses">
<a href="./all-courses.html">课程</a>
</li>
<li class="career-planning">
<a href="./career-planning.html">职业规划</a>
</li>
</ul>
<div class="search-bar">
<input type="text" name="" id="" placeholder="输入关键字" />
<input type="button" value="" style={
{
background: {
faSearch } }} />
</div>
<div class="user flex">
<div class="user-center">个人中心</div>
<div class="alert">
<a href="#">
<img src={
ld} alt="" />{
' '}
</a>
</div>
<div class="profile-img">
<img src={
profile} alt="profile-image" />
</div>
<div class="username">qq-leishui</div>
</div>
</div>
</div>
);
};
export default index;
渲染后的结果:
写到这里应该就已经有人意识到,为什么明明写的是 JavaScript,语法看起来和 HTML 这么像。
这就是 React 封装的语法糖,用类似 HTML 的结构去渲染页面。这也是我觉得 React 上手其实还挺快的原因。
那可能又有人在想,既然直接写 HTML 也可以工作,为什么还要拆分这么多组件?
这就以 Header 左上角的 Logo 为例,我突然发现这个 Logo 会同时在 Header 和 Footer 中被用到,所以临时将其抽离出来,做成单独的一个组件让 Header 和 Footer 去用。
Logo 的内容其实很简单,就是名为 logo 的 div,拆出来其实只有七八行代码:
import pic from '../../asset/img/header/logo.png';
import React from 'react';
const Logo = () => {
return (
<div className="logo">
<img src={
pic} alt="logo" />
</div>
);
};
export default Logo;
修改 Header,在 Header 中引用 Logo:
import React from 'react';
import '../../index.css';
import './header.css';
import faSearch from '../../asset/img/header/fa-search.png';
import ld from '../../asset/img/header/ld.png';
import profile from '../../asset/img/header/pic.png';
import Logo from '../logo';
const index = () => {
return (
<div className="header flex">
<Logo />
{
/* 后面代码省略 */}
</div>
);
};
export default index;
修改 Footer,在 Footer 中引入 Logo。
注,以下代码是不完全实现,Footer 的完整实现在后文。
import React from 'react';
import '../../index.css';
import './footer.css';
import Logo from '../logo';
const Footer = () => {
return (
<div>
<Logo />
</div>
);
};
这时候就能看到复用的好处了吧,只需要导入已经写好的组件,就可以实现复用的效果,而不是自己再复制黏贴一遍。
同样,如果哪一天的需求是修改 Logo 的图片了,也只需要在 Logo 组件之中修改即可,而不用满世界的到处去寻找所有图片的引用,减少人工错误。
同样的,也将 menu 抽离出来单独做一个组件。
为了减少手动的复制黏贴,我这里新建了一个对象,保存的是所有 menu 中子项的中文名,以及其对应的 url 地址。url 是直接引用在路由中封装好的字符串:
import React from 'react';
import * as routePaths from '../../../router/routerPaths';
import {
Link } from 'react-router-dom';
const LINKS = [
{
name: '首页', url: routePaths.INDEX },
{
name: '课程', url: routePaths.COURSE },
{
name: '职业规划', url: routePaths.CAREER_PATH },
];
const Nav = () => {
return (
<ul className="menu flex">
{
LINKS.map((link) => (
<li>
<Link to={
link.url}>{
link.name}</Link>
</li>
))}
</ul>
);
};
export default Nav;
这里贴一下原生的 HTML 与现在的 JSX 的代码对比:
|
import React from 'react'; import * as routePaths from '../../routerrouterPaths'; import { Link } from 'react-router-dom'; |
const LINKS = [
{ name: ‘首页’, url: routePaths.INDEX },
{ name: ‘课程’, url: routePaths.COURSE },
{ name: ‘职业规划’, url: routePaths.CAREER_PATH },
];
const Nav = () => {
return (
export default Nav;
使用 JSX 的优势在于,可以动态的接收数据。
动态接收数据指的是,假设所有的内容不是手写出来的,而是存储在某些地方,那么对于开发来说就没有办法一行行写死代码——毕竟连多少数据量都不知道。
这时候的 Header
import React from 'react';
import '../../index.css';
import './header.css';
import faSearch from '../../asset/img/header/fa-search.png';
import ld from '../../asset/img/header/ld.png';
import profile from '../../asset/img/header/pic.png';
import Logo from '../logo';
import Nav from './Nav/Nav';
const index = () => {
return (
<div className="header flex">
<Logo />
<div className="container flex">
<Nav />
{
/* 后面代码省略 */}
</div>
</div>
);
};
export default index;
这时候从 Header 页面也能一眼看出来,这个页面分成了若干模块:
使用 JSX 和直接使用原生 HTML 的对比,已经慢慢变得明显了。
注:写于组件实现之后:写完后我发现 menu 其实就是 nav,所以在后面修改警告的时候也对其进行了一些修正。详情可以看最后的完整实例部分。
按照这个模式继续修改,抽出 search 组件
import React from 'react';
import faSearch from '../../../asset/img/header/fa-search.png';
const SearchBar = () => {
return (
<div class="search-bar">
<input type="text" name="" id="" placeholder="输入关键字" />
<input
type="button"
value=""
style={
{
background: `url({ faSearch })` }}
/>
</div>
);
};
export default SearchBar;
import React from 'react';
import ld from '../../../asset/img/header/ld.png';
import profile from '../../../asset/img/header/pic.png';
const UserProfile = () => {
return (
<div class="user flex">
<div class="user-center">个人中心</div>
<div class="alert">
<a href="#">
<img src={
ld} alt="" />{
' '}
</a>
</div>
<div class="profile-img">
<img src={
profile} alt="profile-image" />
</div>
<div class="username">qq-leishui</div>
</div>
);
};
export default UserProfile;
清理过后的 Header 其实也很短,并且一眼能够看出包含了几个组件:
import React from 'react';
import '../../index.css';
import './header.css';
import Logo from '../logo';
import Nav from './nav/Nav';
import SearchBar from './searchBar';
import UserProfile from './userProfile';
const index = () => {
return (
<div className="header flex">
<Logo />
<Nav />
<SearchBar />
<UserProfile />
</div>
);
};
export default index;
至此,Header 的结构实现就完成了
Header 写完了,本期的任务就剩下 Footer 了。
老规矩,先把 Footer 的 HTML 复制黏贴进来:
import React from 'react';
import '../../index.css';
import './footer.css';
const Footer = () => {
return (
<div class="footer">
<div class="container flex">
<div class="copyright">
<img src="./img/logo.png" alt="logo" />
<p>
学成在线致力于普及中国最好的教育。它与中国一流大学和机构合作,提供在线课程。
<br />© 2017年 XTGG Inc. 保留所有权利。-沪ICP备11111111号
</p>
<a href="#" class="app">
下载APP
</a>
</div>
<div class="links flex">
<dl>
<dt>关于学成网</dt>
<dd>
<a href="#">关于</a>
</dd>
<dd>
<a href="#">团队管理</a>
</dd>
<dd>
<a href="#">工作机会</a>
</dd>
<dd>
<a href="#">客户服务</a>
</dd>
<dd>
<a href="#">帮助</a>
</dd>
</dl>
<dl>
<dt>关于学成网</dt>
<dd>
<a href="#">关于</a>
</dd>
<dd>
<a href="#">团队管理</a>
</dd>
<dd>
<a href="#">工作机会</a>
</dd>
<dd>
<a href="#">客户服务</a>
</dd>
<dd>
<a href="#">帮助</a>
</dd>
</dl>
<dl>
<dt>关于学成网</dt>
<dd>
<a href="#">关于</a>
</dd>
<dd>
<a href="#">团队管理</a>
</dd>
<dd>
<a href="#">工作机会</a>
</dd>
<dd>
<a href="#">客户服务</a>
</dd>
<dd>
<a href="#">帮助</a>
</dd>
</dl>
</div>
</div>
</div>
);
};
export default Footer;
代码已经复制黏贴进来了,那么继续拆分组件
import React from 'react';
import Logo from '../../logo';
const Copyright = () => {
return (
<div className="copyright">
<Logo />
<p>
学成在线致力于普及中国最好的教育。它与中国一流大学和机构合作,提供在线课程。
<br />© 2017年 XTGG Inc. 保留所有权利。-沪ICP备11111111号
</p>
<a href="./" className="app">
下载APP
</a>
</div>
);
};
export default Copyright;
这里我拆了两个组件出来,对于这种简单的业务来说,拆一个问题也不大。
footerlinks 是 footer 中所有 dl 组成的数组
具体实现为:
import React from 'react';
import FOOTER_LINKS from '../../../constant/footerLinks';
import FooterLink from './FooterLink';
const FooterLinks = () => {
const footerLinkArray = [];
for (const link in FOOTER_LINKS) {
footerLinkArray.push(
<FooterLink subLinks={
FOOTER_LINKS[link]} key={
link} />
);
}
return <div className="links flex">{
footerLinkArray}</div>;
};
export default FooterLinks;
另外,我将上面的一串 dl > dt +dd
的部分也单独抽离出来,放在了一个常量之中。
数据结构为:
import * as routePaths from './routerPaths';
const FOOTER_LINKS = {
about: {
key: '关于学成网',
options: [
{
url: routePaths.INDEX,
name: '关于',
},
{
url: routePaths.INDEX,
name: '团队管理',
},
{
url: routePaths.INDEX,
name: '工作机会',
},
{
url: routePaths.INDEX,
name: '客户服务',
},
{
url: routePaths.INDEX,
name: '帮助',
},
],
},
userGuide: {
key: '新手指南',
options: [
{
url: routePaths.INDEX,
name: '如何注册',
},
{
url: routePaths.INDEX,
name: '如何选课',
},
{
url: routePaths.INDEX,
name: '如何拿到毕业证',
},
{
url: routePaths.INDEX,
name: '学分是什么',
},
{
url: routePaths.INDEX,
name: '考试未通过怎么办',
},
],
},
parterner: {
key: '合作伙伴',
options: [
{
url: routePaths.INDEX,
name: '合作机构',
},
{
url: routePaths.INDEX,
name: '合作导师',
},
],
},
};
export default FOOTER_LINKS;
这里是单独的一个 link 的业务实现,也就是一个 dl>dt+dd
的逻辑
import React from 'react';
const FooterLink = (props) => {
const key = props?.subLinks?.key;
const options = props?.subLinks?.options;
return (
<dl>
<dt>{
key}</dt>
{
options?.map((opt) => (
<dd {
opt.name}>
<a href={
opt.url}>{
opt.name}</a>
</dd>
))}
</dl>
);
};
export default FooterLink;
在结构完成之后,先打开浏览器看一下效果,并且根据下面的控制台修改一下 warnings。
没有 CSS 的效果是这样的:
确实不大好看,不过这点会在 CSS 中被修正。
另外,开始清 warnings 之后才发现,自己粗心的地方还蛮多的……
主要是两个方向:
之前所有的 class 属于 HTML 的标记,是 JSX 中的保留词。为了防止出错,JSX 中的对应名称为 className,所以需要将所有的 class 修改为 className
没有在循环中加入 key,这个警告是在 nav 组件中
循环体里加 key 是 React 所具有的特性之一,属于性能优化类。不改虽然不影响渲染,在数据量大的情况下会影响性能。
注:最后完整版放的代码都是经过修改的
这一部分主要是增加一些全局会使用的 CSS,包括 a 标签、li 标签的一些排版问题,目前实现的 CSS 如下:
body {
margin: 0;
width: 1980px;
background-color: #f3f5f7;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
li {
list-style: none;
}
input {
outline: none;
}
a {
text-decoration: none;
color: #050505;
}
a:hover {
color: #00a4ff;
}
dd {
margin: 0;
}
.flex {
display: flex;
}
.relative {
position: relative;
}
.absolute {
position: absolute;
}
.container {
width: 1200px;
margin: auto;
background-color: transparent;
}
.flex-center {
align-items: center;
}
.float-left {
float: left;
}
.float-right {
float: right;
}
鉴于 Logo 已经拆分出去了,所以就为它添加单独的 css 文件了。很短,只是确定了 Logo 的高度:
.logo {
width: 390px;
}
这个部分调整的还是蛮快的,所有的 CSS 修改都在 header.css 文件之中。当然,最终在实现 CSS 的时候会发现,其实有一些的 HTML 部分也可以做相应的细微调整,以达到比较好的代码复用效果。
修改 Header 主体部分的 CSS,主要是调整了高度和 margin:
.header {
height: 42px;
margin: 30px auto;
}
logo 的图片有向右浮动的效果,这是只有 header 中的 logo 有,footer 中的 logo 没有的,因此加到这里:
.header .logo img {
float: right;
}
给 nav 部分添加左右间距,同时也给 nav 中的每个 li 设置内外边距,增强展现效果:
.nav {
margin-right: 300px;
font-size: 18px;
text-align: center;
}
.nav li {
margin-left: 65px;
padding: 0 8px;
}
修改完后的结果:
JSX 中的修改是在引入的时候做的修改,主要是 style 这里:
style={
{
backgroundImage: `url(${
faSearch})` }}
这是属于 JSX 的行内元素的写入方法,毕竟 JSX 是 JavaScript 的语法糖,所以其中 style 接受的参数必须要是一个 JavaScript 的对象。同样的,CSS 属性 background-image
被修改为了对应的驼峰格式:backgroundImage
。
url 部分使用的是 ES6 后的新属性,也就是模板字符串,优势在于拼接变量方便一些。
.search-bar input[type='text'] {
box-sizing: border-box;
width: 360px;
height: 42px;
border: 1px solid #00a4ff;
border-right: 0;
background-color: #f3f5f7;
color: #bfbfbf;
font-size: 14px;
padding-left: 15px;
}
.search-bar input[type='button'] {
margin-right: 32px;
float: right;
width: 50px;
height: 42px;
border: 0;
}
CSS 实现的部分有以下几个功能:修改样式字体、内外间距,以及将按钮和输入框放在同一行(通过 float 实现)
主要也是调整了字体大小、内外间距,以及图片透明度和实现圆边框功能
.user {
font-size: 14px;
}
.user-center {
margin-right: 32px;
}
.alert {
width: 14px;
}
.alert img {
opacity: 0.6;
}
.alert img,
.user-profile img {
transform: translateY(3px);
}
.user-profile {
margin-left: 30px;
margin-right: 6px;
}
.user-profile img {
border-radius: 50%;
}
页面实现后:
这里主要就是对 copyright 和 footerlinks 的布局进行修改。使用的是 flex,又想拟态 float-left 和 float-right 的效果,就是用了 justify-content: space-between;
.footer {
height: 100px;
}
.footer .container {
justify-content: space-between;
}
其实写到这里就会发现,CSS 大部分的样式调整的东西都是大同小异的。
.copyright p {
font-size: 12px;
color: #666;
margin: 20px 0 15px 0;
}
.download-app {
display: inline-block;
width: 118px;
height: 33px;
text-align: center;
line-height: 33px;
border: 1px solid #00a4ff;
font-style: 16px;
color: #00a4ff;
}
.footer-links dl {
margin-left: 80px;
padding: 0 30px;
color: #333;
}
.footer-links dt {
font-size: 16px;
margin-bottom: 5px;
}
.footer-links dd a {
font-size: 12px;
color: #333;
}
这时候的效果:
到这里,教程部分就结束了,下面放一下完整的实现部分。
主要的结构就是这样的,接下来会按照模块的顺序贴完整代码
app.css 其实现在没东西,所以就不放了
import Routes from './router/routes';
function App() {
return (
<div className="App">
<Routes />
</div>
);
}
export default App;
body {
margin: 0;
width: 1980px;
background-color: #f3f5f7;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
li {
list-style: none;
}
input {
outline: none;
}
a {
text-decoration: none;
color: #050505;
}
a:hover {
color: #00a4ff;
}
dd {
margin: 0;
}
.flex {
display: flex;
}
.relative {
position: relative;
}
.absolute {
position: absolute;
}
.container {
width: 1200px;
margin: auto;
background-color: transparent;
}
.flex-center {
align-items: center;
}
.float-left {
float: left;
}
.float-right {
float: right;
}
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')
);
这是负责热部署,在 React v17 会出现热部署失效的功能,加上这个再重新启动一下项目就好了。
FAST_REFRESH=false
就一些图片,目前只有 header 要用的(logo 图片没有抽出去)
其中,目前 banner 和 course-item 是空的,就不加进去了
footer 模块中的完整代码,默认是 index.js 文件
import React from 'react';
import './footer.css';
import Copyright from './copyright';
import FooterLinks from './FooterLinks';
const Footer = () => {
return (
<footer className="footer">
<div className="container flex">
<Copyright />
<FooterLinks />
</div>
</footer>
);
};
export default Footer;
.footer {
height: 100px;
}
.footer .container {
justify-content: space-between;
}
/* copyright */
.copyright p {
font-size: 12px;
color: #666;
margin: 20px 0 15px 0;
}
.download-app {
display: inline-block;
width: 118px;
height: 33px;
text-align: center;
line-height: 33px;
border: 1px solid #00a4ff;
font-style: 16px;
color: #00a4ff;
}
/* copyright */
/* footer-links */
.footer-links dl {
margin-left: 80px;
padding: 0 30px;
color: #333;
}
.footer-links dt {
font-size: 16px;
margin-bottom: 5px;
}
.footer-links dd a {
font-size: 12px;
color: #333;
}
/* footer-links */
import React from 'react';
import Logo from '../../logo';
const Copyright = () => {
return (
<div className="copyright">
<Logo />
<p>
学成在线致力于普及中国最好的教育。它与中国一流大学和机构合作,提供在线课程。
<br />© 2017年 XTGG Inc. 保留所有权利。-沪ICP备11111111号
</p>
<a href="./" className="download-app">
下载APP
</a>
</div>
);
};
export default Copyright;
import React from 'react';
import FOOTER_LINKS from '../../../constants/footerLinks';
import FooterLink from './FooterLink';
const FooterLinks = () => {
const footerLinkArray = [];
for (const link in FOOTER_LINKS) {
footerLinkArray.push(
<FooterLink subLinks={
FOOTER_LINKS[link]} key={
link} />
);
}
return <div className="footer-links flex">{
footerLinkArray}</div>;
};
export default FooterLinks;
import React from 'react';
const FooterLink = (props) => {
const key = props?.subLinks?.key;
const options = props?.subLinks?.options;
return (
<dl>
<dt>{
key}</dt>
{
options?.map((opt) => (
<dd key={
opt.name}>
<a href={
opt.url}>{
opt.name}</a>
</dd>
))}
</dl>
);
};
export default FooterLink;
import React from 'react';
import NAV_LINKS from '../../../constants/navLinks';
import {
Link } from 'react-router-dom';
const Nav = () => {
return (
<nav className="nav">
<ul className="flex">
{
NAV_LINKS.map((link) => (
<li key={
link.name}>
<Link to={
link.url}>{
link.name}</Link>
</li>
))}
</ul>
</nav>
);
};
export default Nav;
import React from 'react';
import faSearch from '../../../asset/img/header/fa-search.png';
const SearchBar = () => {
return (
<div className="search-bar">
<input type="text" name="" id="" placeholder="输入关键字" />
<input
type="button"
value=""
style={
{
backgroundImage: `url(${
faSearch})` }}
/>
</div>
);
};
export default SearchBar;
import React from 'react';
import ld from '../../../asset/img/header/ld.png';
import profile from '../../../asset/img/header/pic.png';
const UserProvile = () => {
return (
<div className="user flex flex-center">
<div className="user-center">个人中心</div>
<div className="alert">
<a href="./">
<img src={
ld} alt="" />{
' '}
</a>
</div>
<div className="user-profile">
<img src={
profile} alt="user-profile" />
</div>
<div className="username">qq-leishui</div>
</div>
);
};
export default UserProvile;
import React from 'react';
import './header.css';
import Logo from '../logo';
import Nav from './nav';
import SearchBar from './searchBar';
import UserProvile from './userProfile';
const index = () => {
return (
<header className="header flex flex-center">
<Logo />
<Nav />
<SearchBar />
<UserProvile />
</header>
);
};
export default index;
.header {
height: 42px;
margin: 30px auto;
}
/* logo */
.header .logo img {
float: right;
}
/* logo */
/* nav */
.nav {
margin-right: 300px;
font-size: 18px;
text-align: center;
}
.nav li {
margin-left: 65px;
padding: 0 8px;
}
/* nav */
/* search bar */
.search-bar input[type='text'] {
box-sizing: border-box;
width: 360px;
height: 42px;
border: 1px solid #00a4ff;
border-right: 0;
background-color: #f3f5f7;
color: #bfbfbf;
font-size: 14px;
padding-left: 15px;
}
.search-bar input[type='button'] {
margin-right: 32px;
float: right;
width: 50px;
height: 42px;
border: 0;
}
/* search bar */
/* user profile */
.user {
font-size: 14px;
}
.user-center {
margin-right: 32px;
}
.alert {
width: 14px;
}
.alert img {
opacity: 0.6;
}
.alert img,
.user-profile img {
transform: translateY(3px);
}
.user-profile {
margin-left: 30px;
margin-right: 6px;
}
.user-profile img {
border-radius: 50%;
}
/* user profile */
比较短,就不分了,直接贴 CSS 和 JSX
import pic from '../../asset/img/header/logo.png';
import React from 'react';
import './logo.css';
const Logo = () => {
return (
<div className="logo">
<img src={
pic} alt="logo" />
</div>
);
};
export default Logo;
.logo {
width: 390px;
}
import React from 'react';
import Footer from '../footer';
import Header from '../header/index';
export default function HeaderFooterHOC(WrappedComp) {
class HOC extends React.Component {
render() {
return (
<>
<Header />
<WrappedComp />
<Footer />
</>
);
}
}
return HOC;
}
其实目前只有一个 placeholder:
import React from 'react';
const Home = () => {
return <div>Home</div>;
};
export default Home;
将一些常量/假数据抽离出来了:
import * as routePaths from './routerPaths';
const FOOTER_LINKS = {
about: {
key: '关于学成网',
options: [
{
url: routePaths.INDEX,
name: '关于',
},
{
url: routePaths.INDEX,
name: '团队管理',
},
{
url: routePaths.INDEX,
name: '工作机会',
},
{
url: routePaths.INDEX,
name: '客户服务',
},
{
url: routePaths.INDEX,
name: '帮助',
},
],
},
userGuide: {
key: '新手指南',
options: [
{
url: routePaths.INDEX,
name: '如何注册',
},
{
url: routePaths.INDEX,
name: '如何选课',
},
{
url: routePaths.INDEX,
name: '如何拿到毕业证',
},
{
url: routePaths.INDEX,
name: '学分是什么',
},
{
url: routePaths.INDEX,
name: '考试未通过怎么办',
},
],
},
parterner: {
key: '合作伙伴',
options: [
{
url: routePaths.INDEX,
name: '合作机构',
},
{
url: routePaths.INDEX,
name: '合作导师',
},
],
},
};
export default FOOTER_LINKS;
import * as routePaths from './routerPaths';
const NAV_LINKS = [
{
name: '首页', url: routePaths.INDEX },
{
name: '课程', url: routePaths.COURSES },
{
name: '职业规划', url: routePaths.CAREER_PATH },
];
export default NAV_LINKS;
export const INDEX = '/';
export const CAREER_PATH = '/career-path';
export const COURSES = '/courses';
export const COURSE = '/courses/:id';
这个和最上面放的是完全一样(没有动过),所以只放一个案例在这了:
import React from 'react';
import HeaderFooterHOC from '../../common/renderWithHeaderFooter';
import Home from '../../components/home/Home';
const HomeIndex = () => {
return (
<div>
<Home />
</div>
);
};
export default HeaderFooterHOC(HomeIndex);
import {
BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import * as routePaths from '../constants/routerPaths';
import CareerPath from '../containers/careerPath';
import Home from '../containers/home';
import Courses from '../containers/courses';
import Course from '../containers/course';
const routes = () => {
return (
<Router>
<Switch>
<Route path={
routePaths.INDEX} exact component={
Home} />
<Route path={
routePaths.CAREER_PATH} exact component={
CareerPath} />
<Route path={
routePaths.COURSES} exact component={
Courses} />
<Route path={
routePaths.COURSE} exact component={
Course} />
</Switch>
</Router>
);
};
export default routes;