《React打造精美WebApp》(试读)

7,090 阅读11分钟

点击进入GitHub仓库 (欢迎来star、提pr哦!)

点击进入项目上线地址 项目支持PWA,点击chrome或者safari浏览器的添加至主屏幕,即可在桌面跟App一样以图标形式访问,体验如丝滑般流畅:)

第一章:项目说明及初始化

一、前言

为什么做这个项目(React音乐WebApp)?

首先说一说为什么要做这样一个React开源项目:

  • 1、后端接口和文档完整,大家都知道开发一个产品绝不仅仅是前端一个人的活,需要花大量的时间和后端沟通,商讨数据接口的形式,最终做成文档并以此为依据开发。而现在github已经有非常成熟的抓取网易云音乐服务端的接口,不仅在与时俱进地不断更新接口,而且文档写的也非常完善,用这样的真实数据接口可以大大方便我们进行前端的开发,大大降低了我们开发前端的成本。

  • 2、React Hooks如今可谓前端界"当红小生", 因其API简洁性、逻辑复用性等特性逐渐被开发者所应用,未来的vue3.0也是采用类似的Function Based的模式,因此学习React Hooks也是未来的大趋势,通过一个具体的项目来实践、应用hooks特性,我觉得比干啃文档要强太多,并且在实践的过程中会遇到一些坑,通过坑驱动来学习,可以加深我们对于hooks原理的理解。

  • 3、把Redux和不可变数据结合也是react性能优化的一个重要的手段,我很早就想用Redux结合Facebook开源的一款不可变数据结构库immutable.js来开发,所以通过这个项目来具体地实践一把。

为什么做这个教程?

  • 1、 首先在这个项目发出时,我就已经做了一个Promise, 拆解文章一定会出来,这是直接原因。

  • 2、 我相信这个系列出来能够帮助不少的前端开发者,能够通过自己的才华和努力影响一批人,我觉得这本来就是一件非常有成就感的事情。

  • 3、 输出倒逼输入。这个项目不是我一切准备就绪才开始开发的,而是一步步尝试、不断学新的东西,才得以呈现现在的效果。我想写拆解文章复盘,也是这样一个过程,让自己能够不断产生新的想法,然后去尝试,对自己本身也是一种成长。

二、项目介绍

项目整体架构

这是我们即将开发的版本,大家可以看到,这个项目还是稍微有一些复杂的,相信大家跟着做完会有非常大的收获。

技术栈分解

  • react v16.8: 用于构建用户界面的 MVVM 框架
  • redux: 著名JavaScript状态管理容器
  • redux-thunk: 处理异步逻辑的redux中间件
  • immutable: Facebook历时三年开发出的进行持久性数据结构处理的库 (它和memo、Redux搭配就是神器,memo包裹函数组件跟PureComponent是一样的效果,在组件更新前进行数据的浅层比较,具体请参考这篇文章当 PureComponent 遇上 ImmutableJS)
  • react-lazyload: react懒加载库
  • better-scroll: 提升移动端滑动体验的知名库
  • styled-components: 处理样式,体现css in js的前端工程化神器(详情请移步我之前的文章styled-components:前端组件拆分新思路)
  • axios: 用来请求后端api的数据

开发代码风格

在开始这个项目之前,我有必要强调一个这个项目工程的开发规范和我个人的编码风格,提前告知一下,我这么做也是有自己充分的理由的,让项目可读性和可维护性尽可能高,希望后面看到一些奇葩的操作不要感到奇怪。

1、class组件不再用,全面拥抱hooks,统一用函数组件。

2、组件内部状态用hooks处理,凡是业务数据全部放在redux中管理。

3、ajax请求以及后续数据处理的具体代码全部放在actionCreator中,由redux-thunk进行处理,尽可能精简组件代码。

4、每一个容器组件都有自己独立的reducer,然后再全局的store下通过redux的combineReducer方法合并。

5、JS变量名(包括函数名)采用小驼峰的方式,组件名或者styled-components导出的样式容器名都采用大驼峰,常量名所有字母大写。

6、普通CSS类名全部用英语小写,单词间用下划线连接,CSS动画钩子类名中单词用-连接。

7、凡是props中有数据的,全部在组件最前面提前解构赋值,并且,获得的属性名和方法名要分开声明,从父组件获得的props和通过react-redux中映射获得的props也要分开声明。

8、useEffect统一写在最前面,并且紧跟着props解构赋值代码后面。

9、凡是负责返回JSX的函数,统一聚集在函数最后面,中间不要穿插事件处理函数和其他逻辑。

10、mapDispatchToProps返回的函数中,函数名格式为xxxDispatch,以免和现有action名冲突。

通过这个项目你能获得哪些提升?

    1. 熟练使用React Hooks进行业务开发,理解哪些场景产生闭包陷阱,如何避免掉坑。
    1. 掌握React + Redux的工程化编码的全流程。
    1. 封装常用的移动端组件,实现常见的需求,如封装滚动组件、实现图片懒加载、实现上拉/下拉刷新的功能、实现防抖功能、实现组件代码分割(CodeSpliting)等等(可参考项目架构图)。
    1. 拥有实现前端复杂交互的实际项目经验,提升自己的内功,比如开发播放器内核就是其中一个很大很大的挑战。
    1. 掌握CSS中的诸多技巧,提升自己的CSS能力,无论布局还是动画,都有相当多的实践和探索,并且一种效果会给出多种不同的方案,大家打开项目预览地址可以自己体会。
    1. 封装JS第三方包,实现从开发、测试到发布全流程。
    1. 彻底理解redux原理,并能够独立开发redux的中间件。

看到这里你是不是有一点心动?哈哈,别高兴的太早。其实要掌握这些内容着实不容易,要经历的还有很多很多,我个人能力也有限,无法保证你100%能够消化掉这些东西,但我唯一可以保证的是:

这个系列拆解文章我会百分之百地投入,将我在开发中所经历的坑和解决问题的过程毫无保留地分享给各位,对于一些大家可能会感到陌生的概念和语法都会一五一十地摊开来说,做到真正的"手摸手"。对于里面涉及的代码,我首先会再markdown中自己写一遍,然后把自己当作小白,对照vuepress博客的开发界面再自己从头到尾实现一遍,给大家足够的技术安全感。

但是本系列拆解文章也不是针对前端纯小白的,由于基础部分过于科普太多基础内容很多小伙伴会厌倦,所以需要你有一定的前端知识的储备,具体来说是指CSS常用的布局方式和属性,原生JavaScript的基础,ES6的常用语法,React和Redux的基本使用。

推荐学习资料

如果有些你还不会,我推荐你先去学习一下这些资料:

阮一峰ES6入门

技术胖React全家桶及React Hooks完整免费教程

三、初始化项目

本小节代码大家可以去参考GitHub仓库chapter1分支。传送门

create-react-app脚手架初始化

首先通过create-react-app这个脚手架工具生成项目的初始化化结构,在命令行中输入以下命令:

create-react-app cloud-music

完成后,根据提示:

cd cloud-music

启动项目:

npm start

项目目录说明

开始这个项目之前,我们需要对目录进行一下改造。如下(主要针对src目录):

├─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:前端组件拆分新思路。当然后面有人看了我的项目后给我提了这个库的一些缺点,但我依然坚持用它进行开发,因为它在工程化方面的优势依然非常明显,而且大部分缺点我们也可以有意识的去避开,这个具体在后面的章节里面再说吧。

其实styled-components的使用是相当简单的,不需要额外专门的学习,所以大家跟着我写一遍,熟悉一下就行了。

不知道你有没有发现一个问题,上面目录中默认样式文件是style.js,而不是.css,没错,这就是使用了styled-components后的结果。

我们先安装这个库:

npm install styled-components --save

在刚刚的style.js中,

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创建全局样式并导出的代码。

这段代码导出到哪里去呢?导入到App.js中。

//App.js中添加这一句
import { GlobalStyle } from  './style';

我们继续来引入字体图标文件,这里的字体图标是采用的阿里图标库地址

选择好图标之后下载至本地(本项目下载unicode模式)。这个操作不属于本项目的重点,也过于简单,就不在这浪费篇幅了。

在assets目录下新建一个名为iconfont的文件夹, 将.css, .eot, .svg, .ttf, .woff为后缀的文件放到这个文件夹中。 然后将这个css文件做一些手脚,需要改成js代码。

所以现在的iconfont.css需要改成iconfont.js,这里做了一些省略,具体代码大家直接看GitHub仓库chapter1分支吧。

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' ...省略base64巨长字符) format('embedded-opentype'), /* IE6-IE8 */
  url('data:application/x-font-woff2;charset=utf-8) 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中。

//App.js
import React from 'react';
import { IconStyle } from './assets/iconfont/iconfont';
import { GlobalStyle } from './style';

function App() {
  return (
    <div className="App">
      <GlobalStyle></GlobalStyle>
      <IconStyle></IconStyle>
      <i className="iconfont">&#xe62b;</i>
    </div>
  );
}

export default App;

接下来大家打开页面可以看到一个小小的放大镜,背景变为浅灰色,字体图标和默认样式起到了效果。

到此为止,默认样式和字体图标就算一同引入到了项目中。大家可能会字体图标的用法有了了解,但是中间的unicode编码怎么来的呢?别担心,我专门在iconfont文件夹中放了demo_index.html文件,打开便能索引不同图标的unicode值啦。