骨架屏真是个装逼神器

Start

为了更好地接入大型项目,这个章节我们准备接入React作为我们地前端框架,并且引入一个新鲜地概念——骨架屏,我们一起来看看这都是什么吧~

React

在配置webpack前,我们思考一下,我们需要哪些东西?

  1. 能转译jsx语法的babel
  2. 识别React语法的ESlint
  3. React相关依赖

只需要这些我们就能够完成React的简单配置

安装依赖

1
2
npm install react react-dom
npm install react babel-preset-react eslint-plugin-react -D

结合我们之前所学内容,从字面上来看就能够猜出他们都有哪些用处,这里就不一一介绍,如果不熟悉可以查阅我之前的博客。

配置webpack.config.js

React的代码都是写在.js文件当中,也就是babel去处理.js文件,上一节我们已经配置完了babel,所以我们只需要在.babelrc中添加babel对React的支持:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "safari >= 7"]
},
"modules": "commonjs",
"debug": true,
"include": ["transform-es2015-arrow-functions"],
"exclude": ["transform-es2015-for-of"]
}],
"react"
],
"plugins": ["transform-runtime"]
}

我们在presets中加上了react的预设,这样babel就会将React转译为原生的语法。

编写React组件

我们简单的写一个App组件,并在index中引入:

  • App.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from 'react'

class App extends React.Component {
componentDidMount() {
console.log('this is App')
}

render() {
return (
<div>
this is App
</div>
)
}
}

export default App
  • index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import ReactDOM from 'react-dom'
import React from 'react'
import App from './App.js'

class Index extends React.PureComponent {
componentDidMount() {
console.log('this is index')
}

render() {
return (
<div>
<App />
</div>
)
}
}
ReactDOM.render(<Index />, document.querySelector('#root'))

if (module.hot) {
// 实现热更新
module.hot.accept()
}

写过React的都知道,React需要将根组件挂在到Dom上,这个时候我们就还差一步,在模版html中加入:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

运行

配置完后,我们输入npm run dev,查看结果.

骨架屏

这是一个这段时间比较新的概念,他是指在web资源还没加载完成白屏时,展示给用户看的简单图案,这个图案一般都和该web结构差不多,因此称为“骨架屏”:

image

分析

我们在打包后,查看dist中的index.html:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link href="css/main.d3b41950be14d841d949.css" rel="stylesheet"></head>
<body>
<div id="root"></div>
<script type="text/javascript" src="js/bundle.js"></script></body>
</html>

当我们访问到这个页面的时候,首先加载完html文件,再去加载css文件,最终加载js文件,而html文件是一个空的页面,只有js加载完成后,react的组件才会被注入到html中,所以,我们就能在js注入之前,将原本空的dom加上一些结构和样式,在加载js过程中,就能先给用户看到部分内容,而不是完全白屏。

原始做法

可能我们最先想到的是,在<div id="root"></div>中加入内容,比如<div id="root">loading</div>,没错,这样确实可以,js加载完成后,react将<div id="root">loading</div>替换成业务组件。

  • index.html
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    </head>
    <body>
    <div id="root">Loading...</div>
    </body>
    </html>

这个时候,我们就初步完成了一个减缩版“骨架屏”,我们来看下效果:

image

配合Webpack

上面那种方法太不灵活,没法将业务组件和Loading的样式区分开来,这样我们就需要借助Webpack来完成更高级的配置。

我们最早之前为了生成一个html模板的时候,引入了一个插件交html-webpack-plugin,他能够为我们自动生成一个html文件,然后将css和js导入进入,这时,我们在想,是否能够利用他将我们写的loading导入进html模板中。

webpacl.config.js中配置html导入内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module.exports = (env, argv) => {
const devMode = argv.mode === 'development'
// 读取写好的 loading 态的 html 和 css
const loading = {
html: fs.readFileSync(path.join(__dirname, './loading/index.html')),
css: `<style>${fs.readFileSync(path.join(__dirname, './loading/index.css'))}</style>`,
}
return {
...
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
loading,
})
...
],
...
}
}

在根目录下编写loading组件:

  • ./loading/index.html:
1
<div class="loading"></div>
  • ./loading/index.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@keyframes donut-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.loading {
border: 4px solid rgba(0, 0, 0, 0.1);
border-left-color: #7983ff;
border-radius: 50%;
width: 30px;
height: 30px;
animation: donut-spin 1.2s linear infinite;
margin: 0 auto;
margin-top: 45vh;
}

将内容导入模板html中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<%= htmlWebpackPlugin.options.loading.css %>
<title>Document</title>
</head>
<body>
<div id="root">
<%= htmlWebpackPlugin.options.loading.html %>
</div>
</body>
</html>

运行

这样,我们就简单完成了一个类似于“骨架屏”的效果:

image

当然,真正的骨架屏需要按照项目的布局,来做进一步的优化.

Last

这一章节我们完成了React的配置,并且学会了一个比较有趣的技巧,一个简单的loading能够给为用户带来更好的体验,比较白屏确实让人讨厌透顶~

本章节的代码已经上传到Github,传送门webpack-study,请自行切换到chapter-05分支。