仿网易云做的 WebApp(一).md


src 目录改造

1
2
3
4
5
6
7
8
9
10
11
├─api                   // 网路请求代码、工具类函数和相关配置
├─application // 项目核心功能
├─assets // 字体配置及全局样式
├─baseUI // 基础UI轮子
├─components // 可复用的UI组件
├─routes // 路由配置文件
└─store // redux相关文件
App.js // 根组件
index.js // 入口文件
serviceWorker.js // PWA离线应用配置
style.js // 默认样式

项目依赖

安装定义样式的插件

  • styled-components

这个项目利用的是 css in js,所以我们先安装:styled-components

1
$ npm install styled-components --save

它的作用就是让我们能够使用 js 来书写 css 样式。如果是用 vsCode 写这个项目的话,可以搜索 vscode-styled-components 这个插件来辅助我们书写代码。


安装路由插件插件

  • react-router
  • react-router-dom
  • react-router-config
1
$ npm install react-router react-router-dom react-router-config --save

我们利用 react-router-dom 来写路由,而 react-router-config 是静态路由配置的小助手,我们用它来配置我们的静态路由。

这个项目使用的路由是 HashRouter


全局样式

书写全局样式

src 目录的 style.js 里面写入下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import { createGlobalStyle } from 'styled-components';

export const GlobalStyle = createGlobalStyle`
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
html, body{
background: #f2f3f4;;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
a{
text-decoration: none;
color: #fff;
}
`

这段代码是用来定义全局样式的。所有 styled-components 的代码都需要通过引入 styled-components 插件后,通过 export const GlobalStyle = createGlobalStyle 的方式使用,在这里面我们可以书写 CSS 代码,代码的格式类似于 Less

导入全局样式

然后在 App.js 中使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react';
import { IconStyle } from './assets/iconfont/iconfont';
import { GlobalStyle } from './style';

function App() {
return (
<div className="App">
<GlobalStyle></GlobalStyle>
<IconStyle></IconStyle>
<div>组件</div>
</div>
);
}

export default App;

这里的使用方法就是这样,只要把我们的样式组件放在需要该样式的组件的上方就行了。这里的 IconStyle 是我们下面要导入的图标组件。


导入图标

修改图标文件

图标文件可以在我的项目中拿。

srcassets 目录下,创建一个 iconfont 文件夹,里面放我们我们的图标文件,这里的图标来源于阿里图标库,我个人也很喜欢它,个人认为它比起国外的 icomoon 舒服很多。

图标这里没什么好说的,唯一的就是把 iconfont.css 文件改成 iconfont.js 文件,当然,里面的内容也要修改:

iconfont.js 里面引入 styled-components,利用它来把我们的 css 转化成 js,我们保留 @font-face.iconfont 的内容,其他的删掉:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {createGlobalStyle} from 'styled-components';

export const IconStyle = createGlobalStyle`
@font-face {font-family: "iconfont";
src: url('iconfont.eot?t=1565320061289'); /* IE9 */
src: url('iconfont.eot?t=1565320061289#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'),
url('iconfont.woff?t=1565320061289') format('woff'),
url('iconfont.ttf?t=1565320061289') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url('iconfont.svg?t=1565320061289#iconfont') format('svg'); /* iOS 4.1- */
}

.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
`

导入图标文件

然后在 App.js 中使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react';
import { IconStyle } from './assets/iconfont/iconfont';
import { GlobalStyle } from './style';

function App() {
return (
<div className="App">
<GlobalStyle></GlobalStyle>
<IconStyle></IconStyle>
<div>组件</div>
</div>
);
}

export default App;

上面其实已经导入了,这里只是再强调一下使用方法。


路由配置

书写路由文件

src 目录的 routes 文件夹中新建 index.js 文件,利用 react-router-config 来对路由进行配置。

我们在文件中引入:

1
2
3
4
5
6
import React from 'react';
import { Redirect } from 'react-router-dom';
import Home from '../application/Home';
import Recommend from '../application/Recommend';
import Singers from '../application/Singers';
import Rank from '../application/Rank';

react 自不必说,是 JSX 语法必备的,而 react-router-domRedirect 是用来做重定向的插件。

而下面的 HomeRecommendSingersRank 是四个组件,其中 Home 对应的是公共组件,其他三个是具体的功能组件:Recommend 是推荐组件,Singers 是歌手列表组件,Rank 是排行榜组件。

下面写路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
export default [
{
path: '/',
component: Home,
routes: [
{
path: '/',
exact: true,
render: () => (
<Redirect to={'/recommend'} />
)
},
{
path: '/recommend',
component: Recommend
},
{
path: '/singers',
component: Singers
},
{
path: '/rank',
component: Rank
},
]
}
]

第一个路由指定 '/' 是进入的主界面,同时二级路由显示的是 Recommend 组件也就是推荐组件的内容。exact 是精确匹配的意思,当通过 '/' 进入主界面的时候,会进行重定向操作。

导入路由文件

App.js 文件夹导入路由文件:

1
2
3
import routes from './routes';
import { HashRouter } from 'react-router-dom';
import { renderRoutes } from 'react-router-config';

第一个导入的就是我们写的路由配置文件,下面导入的是 react-router-dom,我们只用的 HashRouter 路由组件,然后导入 react-router-config,这个就是用来把我们写的路由配置文件 routes 中的内容转化成 Route 标签的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from 'react';
import { IconStyle } from './assets/iconfont/iconfont';
import { GlobalStyle } from './style';
import routes from './routes';
import { HashRouter } from 'react-router-dom';
import { renderRoutes } from 'react-router-config'; // renderRoutes 读取路由配置转化为 Route 标签

function App() {
return (
<HashRouter>
<GlobalStyle></GlobalStyle>
<IconStyle></IconStyle>
{ renderRoutes(routes) }
</HashRouter>
);
}

export default App;

编写组件以测试效果

application 文件夹下新建 Home 文件夹,然后新建 index.js 文件:

1
2
3
4
5
6
7
8
9
import React from 'react';

function Home(props) {
return (
<div>Home</div>
)
}

export default React.memo(Home);

这里,我们只写了村函数组件,上面的 React.memo 是用来控制组件渲染的,类似于 PureComponent,使用 React.memo 时,组件只会在它的 props 发生变化时重新渲染。它与 PureComponent 不同的是 PureComponent 要依靠 class 才能使用,而 React.memo() 可以和 functional component 一起使用。

类似的编写 RecommendSingersRank 组件。

如果现在启动项目,我们可以看见 Home 的字样,但是我们的 Home 下面应该还有二级路由的 Recommend,而 renderRoutes 这个方法只渲染一层路由,所有我们需要在 Home 组件下再次调用一次 renderRoutes 来渲染第二层界面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react';
import { renderRoutes } from 'react-router-config'

function Home(props) {
const { route } = props;

return (
<div>
<div>Home</div>
{renderRoutes(route.routes)}
</div>
)
}

export default React.memo(Home);

现在就正常显示了。

0%