React 16 加载性能优化指南

2023-11-05

转自:https://zhuanlan.zhihu.com/p/37148975


关于 React 应用加载的优化,其实网上类似的文章已经有太多太多了,随便一搜就是一堆,已经成为了一个老生常谈的问题。

但随着 React 16 和 Webpack 4.0 的发布,很多过去的优化手段其实都或多或少有些“过时”了,而正好最近一段时间,公司的新项目迁移到了 React 16 和 Webpack 4.0,做了很多这方面的优化,所以就写一篇文章来总结一下。

零、基础概念

我们先要明确一次页面加载过程是怎样的(这里我们暂时不讨论服务器端渲染的情况)。


一次渐进式加载的全过程


  1. 用户打开页面,这个时候页面是完全空白的;
  2. 然后 html 和引用的 css 加载完毕,浏览器进行首次渲染,我们把首次渲染需要加载的资源体积称为 “首屏体积”
  3. 然后 react、react-dom、业务代码加载完毕,应用第一次渲染,或者说首次内容渲染
  4. 然后应用的代码开始执行,拉取数据、进行动态import、响应事件等等,完毕后页面进入可交互状态;
  5. 接下来 lazyload 的图片等多媒体内容开始逐渐加载完毕;
  6. 然后直到页面的其它资源(如错误上报组件、打点上报组件等)加载完毕,整个页面的加载就结束了。

所以接下来,我们就分别讨论这些步骤中,有哪些值得优化的点。


一. 打开页面 -> 首屏



写过 React 或者任何 SPA 的你,一定知道目前几乎所有流行的前端框架(React、Vue、Angular),它们的应用启动方式都是极其类似的:

  1. html 中提供一个 root 节点
<div id="root"></div>

2. 把应用挂载到这个节点上

ReactDOM.render(
  <App/>,
  document.getElementById('root')
);

这样的模式,使用 webpack 打包之后,一般就是三个文件:

  1. 一个体积很小、除了提供个 root 节点以外的没什么卵用的html(大概 1-4 KB
  2. 一个体积很大的 js(50 - 1000 KB 不等
  3. 一个 css 文件(当然如果你把 css 打进 js 里了,也可能没有)

这样造成的直接后果就是,用户在 50 - 1000 KB 的 js 文件加载、执行完毕之前,页面是 完!全!空!白!的!

也就是说,这个时候:

首屏体积(首次渲染需要加载的资源体积) = html + js + css

1.1. 在 root 节点中写一些东西

我们完全可以把首屏渲染的时间点提前,比如在你的 root 节点中写一点东西:

<div class="root">Loading...</div>

就是这么简单,就可以把你应用的首屏时间提前到 html、css 加载完毕

此时:

首屏体积 = html + css

当然一行没有样式的 "Loading..." 文本可能会让设计师想揍你一顿,为了避免被揍,我们可以在把 root 节点内的内容画得好看一些:

<div id="root">
    <!-- 这里画一个 SVG -->
</div>

1.2. 使用 html-webpack-plugin 自动插入 loading

实际业务中肯定是有很多很多页面的,每个页面都要我们手动地复制粘贴这么一个 loading 态显然太不优雅了,这时我们可以考虑使用 html-webpack-plugin 来帮助我们自动插入 loading。

var HtmlWebpackPlugin = require('html-webpack-plugin');
var path = require('path');

// 读取写好的 loading 态的 html 和 css
var loading = {
    html: fs.readFileSync(path.join(__dirname, './loading.html')),
    css: '<style>' + fs.readFileSync(path.join(__dirname, './loading.css')) + '</style>'
}

var webpackConfig = {
  entry: 'index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'index_bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'xxxx.html',
      template: 'template.html',
      loading: loading
    })
  ]
};

然后在模板中引用即可:

<!DOCTYPE html>
<html lang="en">
    <head>
        <%= htmlWebpackPlugin.options.loading.css %>
    </head>

    <body>
        <div id="root">
            <%= htmlWebpackPlugin.options.loading.html %>
        </div>
    </body>
</html>

1.3. 使用 prerender-spa-plugin 渲染首屏

在一些比较大型的项目中,Loading 可能本身就是一个 React/Vue 组件,在不做服务器端渲染的情况下,想把一个已经组件化的 Loading 直接写入 html 文件中会很复杂,不过依然有解决办法。

prerender-spa-plugin 是一个可以帮你在构建时就生成页面首屏 html 的一个 webpack 插件,原理大致如下:

  1. 指定 dist 目录和要渲染的路径
  2. 插件在 dist 目录中开启一个静态服务器,并且使用无头浏览器(puppeteer)访问对应的路径,执行 JS,抓取对应路径的 html。
  3. 把抓到的内容写入 html,这样即使没有做服务器端渲染,也能达到跟服务器端渲染几乎相同的作用(不考虑动态数据的话)

具体如何使用,可以参考这一篇文章

plugins: [
  new PrerenderSpaPlugin(
    path.join(__dirname, 'dist'),
    [ '/', '/products/1', '/products/2', '/products/3']
  )
]

1.4. 除掉外链 css

截止到目前,我们的首屏体积 = html + css,依然有优化的空间,那就是把外链的 css 去掉,让浏览器在加载完 html 时,即可渲染首屏。

实际上,webpack 默认就是没有外链 css 的,你什么都不需要做就可以了。当然如果你的项目之前配置了 extract-text-webpack-plugin 或者 mini-css-extract-plugin 来生成独立的 css 文件,直接去掉即可。

有人可能要质疑,把 css 打入 js 包里,会丢失浏览器很多缓存的好处(比如你只改了 js 代码,导致构建出的 js 内容变化,但连带 css 都要一起重新加载一次),这样做真的值得吗?

确实这么做会让 css 无法缓存,但实际上对于现在成熟的前端应用来说,缓存不应该在 js/css 这个维度上区分,而是应该按照“组件”区分,即配合动态 import 缓存组件。

接下来你会看到,css in js 的模式带来的好处远大于这么一丁点缺点。


二. 首屏 -> 首次内容渲染



这一段过程中,浏览器主要在做的事情就是加载、运行 JS 代码,所以如何提升 JS 代码的加载、运行性能,就成为了优化的关键。

几乎所有业务的 JS 代码,都可以大致划分成以下几个大块:

  1. 基础框架,如 React、Vue 等,这些基础框架的代码是不变的,除非升级框架;
  2. Polyfill,对于使用了 ES2015+ 语法的项目来说,为了兼容性,polyfill 是必要的存在;
  3. 业务基础库,业务的一些通用的基础代码,不属于框架,但大部分业务都会使用到;
  4. 业务代码,特点是具体业务自身的逻辑代码。

想要优化这个时间段的性能,也就是要优化上面四种资源的加载速度。


2.1. 缓存基础框架

基础框架代码的特点就是必需不变,是一种非常适合缓存的内容。

所以我们需要做的就是为基础框架代码设置一个尽量长的缓存时间,使用户的浏览器尽量通过缓存加载这些资源。

附:HTTP 缓存资源小结

HTTP 为我们提供了很好几种缓存的解决方案,不妨总结一下:

1. expires

expires: Thu, 16 May 2019 03:05:59 GMT

在 http 头中设置一个过期时间,在这个过期时间之前,浏览器的请求都不会发出,而是自动从缓存中读取文件,除非缓存被清空,或者强制刷新。缺陷在于,服务器时间和用户端时间可能存在不一致,所以 HTTP/1.1 加入了 cache-control 头来改进这个问题。

2. cache-control

cache-control: max-age=31536000

设置过期的时间长度(秒),在这个时间范围内,浏览器请求都会直接读缓存。当 expirescache-control 都存在时,cache-control 的优先级更高。

3. last-modified / if-modified-since

这是一组请求/相应头

响应头:

last-modified: Wed, 16 May 2018 02:57:16 GMT

请求头:

if-modified-since: Wed, 16 May 2018 05:55:38 GMT

服务器端返回资源时,如果头部带上了 last-modified,那么资源下次请求时就会把值加入到请求头 if-modified-since中,服务器可以对比这个值,确定资源是否发生变化,如果没有发生变化,则返回 304。

4. etag / if-none-match

这也是一组请求/相应头

响应头:

etag: "D5FC8B85A045FF720547BC36FC872550"

请求头:

if-none-match: "D5FC8B85A045FF720547BC36FC872550"

原理类似,服务器端返回资源时,如果头部带上了 etag,那么资源下次请求时就会把值加入到请求头 if-none-match 中,服务器可以对比这个值,确定资源是否发生变化,如果没有发生变化,则返回 304。

上面四种缓存的优先级:cache-control > expires > etag > last-modified


2.2. 使用动态 polyfill

Polyfill 的特点是非必需不变,因为对于一台手机来说,需要哪些 polyfill 是固定的,当然也可能完全不需要 polyfill。

现在为了浏览器的兼容性,我们常常引入各种 polyfill,但是在构建时静态地引入 polyfill 存在一些问题,比如对于机型和浏览器版本比较新的用户来说,他们完全不需要 polyfill,引入 polyfill 对于这部分用户来说是多余的,从而造成体积变大和性能损失。

比如 React 16 的代码中依赖了 ES6 的 Map/Set 对象,使用时需要你自己加入 polyfill,但目前几个完备的 Map/Set 的 polyfill 体积都比较大,打包进来会增大很多体积。

还比如 Promise 对象,实际上根据 http://caniuse.com 的数据,移动端上,中国接近 94% 的用户浏览器,都是原生支持 Promise 的,并不需要 polyfill。但实际上我们打包时还是会打包 Promise 的 polyfill,也就是说,我们为了 6% 的用户兼容性,增大了 94% 用户的加载体积。



所以这里的解决方法就是,去掉构建中静态的 polyfill,换而使用 polyfill.io 这样的动态 polyfill 服务,保证只有在需要时,才会引入 polyfill。

具体的使用方法非常简单,只需要外链一个 js:

<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>

当然这样是加载全部的 polyfill,实际上你可能并不需要这么多,比如你只需要 Map/Set 的话:

<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Map,Set"></script>

动态 polyfill 的原理

如果你用最新的 Chrome 浏览器访问这个链接的话:https://cdn.polyfill.io/v2/polyfill.js,你会发现内容几乎是空的:



如果打开控制台,模拟 iOS 的 Safari,再访问一次,你会发现里面就出现了一些 polyfill(URL 对象的 polyfill):



这就是 polyfill.io 的原理,它会根据你的浏览器 UA 头,判断你是否支持某些特性,从而返回给你一个合适的 polyfill。对于最新的 Chrome 浏览器来说,不需要任何 polyfill,所以返回的内容为空。对于 iOS Safari 来说,需要 URL 对象的 polyfill,所以返回了对应的资源。




2.3. 使用 SplitChunksPlugin 自动拆分业务基础库

Webpack 4 抛弃了原有的 CommonChunksPlugin,换成了更为先进的 SplitChunksPlugin,用于提取公用代码。

它们的区别就在于,CommonChunksPlugin 会找到多数模块中都共有的东西,并且把它提取出来(common.js),也就意味着如果你加载了 common.js,那么里面可能会存在一些当前模块不需要的东西。

而 SplitChunksPlugin 采用了完全不同的 heuristics 方法,它会根据模块之间的依赖关系,自动打包出很多很多(而不是单个)通用模块,可以保证加载进来的代码一定是会被依赖到的。

下面是一个简单的例子,假设我们有 4 个 chunk,分别依赖了以下模块:

如果是以前的 CommonChunksPlugin,那么默认配置会把它们打包成下面这样:

显然在这里,react、react-dom、angular 这些公用的模块没有被抽出成为独立的包,存在进一步优化的空间。

现在,新的 SplitChunksPlugin 会把它们打包成以下几个包:


这就保证了所有公用的模块,都会被抽出成为独立的包,几乎完全避免了多页应用中,重复加载相同模块的问题。

具体如何配置 SplitChunksPlugin,请参考 webpack 官方文档

注:目前使用 SplitChunksPlugin 存在的坑

虽然 webpack 4.0 提供的 SplitChunksPlugin 非常好用,但截止到写这篇文章的时候(2018年5月),依然存在一个坑,那就是 html-webpack-plugin 还不完全支持 SplitChunksPlugin,生成的公用模块包还无法自动注入到 html 中。

可以参考下面的 issue 或者 PR:


2.4. 正确使用 Tree Shaking 减少业务代码体积

Tree Shaking 这已经是一个很久很久以前就存在的 webpack 特性了,老生常谈,但事实上不是所有的人(特别是对 webpack 不了解的人)都正确地使用了它,所以我今天要在这里啰嗦地再写一遍。

例如,我们有下面这样一个使用了 ES Module 标准的模块:

// math.js
export function square(x) {
  return x * x
}

export function cube(x) {
  return x * x * x
}

然后你在另一个模块中引用了它:

// index.js
import { cube } from './math'
cube(123)

经过 webpack 打包之后,math.js 会变成下面这样:

/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* unused harmony export square */
/* harmony export (immutable) */ __webpack_exports__["a"] = cube;
function square(x) {
  return x * x;
}

function cube(x) {
  return x * x * x;
}

注意这里 square 函数依然存在,但多了一行 magic comment:unused harmony export square

随后的压缩代码的 uglifyJS 就会识别到这行 magic comment,并且把 square 函数丢弃。

但是一定要注意!!! webpack 2.0 开始原生支持 ES Module,也就是说不需要 babel 把 ES Module 转换成曾经的 commonjs 模块了,想用上 Tree Shaking,请务必关闭 babel 默认的模块转义:

{
  "presets": [
    ["env", {
      "modules": false
      }
    }]
  ]
}

另外,Webpack 4.0 开始,Tree Shaking 对于那些无副作用的模块也会生效了。

如果你的一个模块在 package.json 中说明了这个模块没有副作用(也就是说执行其中的代码不会对环境有任何影响,例如只是声明了一些函数和常量):

{
  "name": "your-module",
  "sideEffects": false
}

那么在引入这个模块,却没有使用它时,webpack 会自动把它 Tree Shaking 丢掉:

import yourModule from 'your-module'
// 下面没有用到 yourModule

这一点对于 lodash、underscore 这样的工具库来说尤其重要,开启了这个特性之后,你现在可以无心理负担地这样写了:

import { capitalize } from 'lodash-es';
document.write(capitalize('yo'));

三、首次内容渲染 -> 可交互



这一段过程中,浏览器主要在做的事情就是加载及初始化各项组件

3.1. Code Splitting

大多数打包器(比如 webpack、rollup、browserify)的作用就是把你的页面代码打包成一个很大的 “bundle”,所有的代码都会在这个 bundle 中。但是,随着应用的复杂度日益提高,bundle 的体积也会越来越大,加载 bundle 的时间也会变长,这就对加载过程中的用户体验造成了很大的负面影响。

为了避免打出过大的 bundle,我们要做的就是切分代码,也就是 Code Splitting,目前几乎所有的打包器都原生支持这个特性。

Code Splitting 可以帮你“懒加载”代码,以提高用户的加载体验,如果你没办法直接减少应用的体积,那么不妨尝试把应用从单个 bundle 拆分成单个 bundle + 多份动态代码的形式。

比如我们可以把下面这种形式:

import { add } from './math';
console.log(add(16, 26));

改写成动态 import 的形式,让首次加载时不去加载 math 模块,从而减少首次加载资源的体积。

import("./math").then(math => {
  console.log(math.add(16, 26));
});

React Loadable 是一个专门用于动态 import 的 React 高阶组件,你可以把任何组件改写为支持动态 import 的形式。

import Loadable from 'react-loadable';
import Loading from './loading-component';

const LoadableComponent = Loadable({
  loader: () => import('./my-component'),
  loading: Loading,
});

export default class App extends React.Component {
  render() {
    return <LoadableComponent/>;
  }
}

上面的代码在首次加载时,会先展示一个 loading-component,然后动态加载 my-component 的代码,组件代码加载完毕之后,便会替换掉 loading-component

下面是一个具体的例子:



以这个用户主页为例,起码有三处组件是不需要首次加载的,而是使用动态加载:标题栏、Tab 栏、列表。首次加载实际上只需要加载中心区域的用户头像、昵称、ID即可。切分之后,首屏 js 体积从 40KB 缩减到了 20KB.

3.2. 编译到 ES2015+ ,提升代码运行效率

相关文章:《Deploying ES2015+ Code in Production Today》

如今大多数项目的做法都是,编写 ES2015+ 标准的代码,然后在构建时编译到 ES5 标准运行。

比如一段非常简洁的 class 语法:

class Foo extends Bar {
    constructor(x) {
        super()
        this.x = x;
    }
}

会被编译成这样:

"use strict";

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var Foo = function (_Bar) {
  _inherits(Foo, _Bar);

  function Foo(x) {
    _classCallCheck(this, Foo);

    var _this = _possibleConstructorReturn(this, (Foo.__proto__ || Object.getPrototypeOf(Foo)).call(this));

    _this.x = x;
    return _this;
  }

  return Foo;
}(Bar);

但实际上,大部分现代浏览器已经原生支持 class 语法,比如 iOS Safari 从 2015 年的 iOS 9.0 开始就支持了,根据 caniuse 的数据,目前移动端上 90% 用户的浏览器都是原生支持 class 语法的:



其它 ES2015 的特性也是同样的情况。

也就是说,在当下 2018 年,对于大部分用户而言,我们根本不需要把代码编译到 ES5,不仅体积大,而且运行速度慢。我们需要做的,就是把代码编译到 ES2015+,然后为少数使用老旧浏览器的用户保留一个 ES5 标准的备胎即可。

具体的解决方法就是 <script type="module"> 标签。

支持 <script type="module"> 的浏览器,必然支持下面的特性:

  • async/await
  • Promise
  • Class
  • 箭头函数、Map/Set、fetch 等等...

而不支持 <script type="module"> 的老旧浏览器,会因为无法识别这个标签,而不去加载 ES2015+ 的代码。另外老旧的浏览器同样无法识别 nomodule 熟悉,会自动忽略它,从而加载 ES5 标准的代码。

简单地归纳为下图:


根据这篇文章,打包后的体积和运行效率都得到了显著提高:



四、可交互 -> 内容加载完毕



这个阶段就很简单了,主要是各种多媒体内容的加载

4.1. LazyLoad

懒加载其实没什么好说的,目前也有一些比较成熟的组件了,自己实现一个也不是特别难:

当然你也可以实现像 Medium 的那种加载体验(好像知乎已经是这样了),即先加载一张低像素的模糊图片,然后等真实图片加载完毕之后,再替换掉。

实际上目前几乎所有 lazyload 组件都不外乎以下两种原理:

  • 监听 window 对象或者父级对象的 scroll 事件,触发 load;
  • 使用 Intersection Observer API 来获取元素的可见性。

4.2. placeholder

我们在加载文本、图片的时候,经常出现“闪屏”的情况,比如图片或者文字还没有加载完毕,此时页面上对应的位置还是完全空着的,然后加载完毕,内容会突然撑开页面,导致“闪屏”的出现,造成不好的体验。

为了避免这种突然撑开的情况,我们要做的就是提前设置占位元素,也就是 placeholder:



已经有一些现成的第三方组件可以用了:

另外还可以参考 Facebook 的这篇文章:《How the Facebook content placeholder works》


五、总结

这篇文章里,我们一共提到了下面这些优化加载的点:

  1. 在 HTML 内实现 Loading 态或者骨架屏;
  2. 去掉外联 css;
  3. 缓存基础框架;
  4. 使用动态 polyfill;
  5. 使用 SplitChunksPlugin 拆分公共代码;
  6. 正确地使用 Webpack 4.0 的 Tree Shaking;
  7. 使用动态 import,切分页面代码,减小首屏 JS 体积;
  8. 编译到 ES2015+,提高代码运行效率,减小体积;
  9. 使用 lazyload 和 placeholder 提升加载体验。

实际上可优化的点还远不止这些,这里推荐一些相关资源给大家阅读:

希望这篇文章,能拯救你下半年的 KPI :)


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

React 16 加载性能优化指南 的相关文章

  • Materialize时间组件datepicker汉化

    首先说明我用的是1 0版本 由于版本差异导致js调用的方法不一致 请注意
  • c# 代码实现通过域名获取IPV4地址

    c 代码实现通过域名获取IPV4地址 IPHostEntry iPHostEntry Dns GetHostByName www baidu com IPAddress ip iPHostEntry AddressList 0 label1
  • Valine 一款快速、简洁且高效的无后端评论系统

    Valine 一款快速 简洁且高效的无后端评论系统 简介 Valine 诞生于2017年8月7日 是一款基于LeanCloud的快速 简洁且高效的无后端评论系统 理论上支持但不限于静态博客 目前已有Hexo Jekyll Typecho H
  • VUE 自定义 穿梭框

    某次项目要使用穿梭框进行数据选择 项目使用的element ui框架 框架中的穿梭框是这样子的 好像不能满足我的需求 因为需要展示很多内容 包括图片等信息 也要加上很多样式等等 我尝试这去改造 一会后觉得还是自己动手去写一个靠谱 几经鼓捣效
  • CSS之Sass中的@mixin和@extend,使用详细(Sass中@mixin和@extend的区别)

    简介 mixin 该指令用于定义可重用的代码块 可以在需要的地方进行调用 通过 mixin指令 你可以将一组样式代码封装成一个mixin 并在需要的地方使用 include指令来调用这个mixin 这样可以避免重复编写相同的样式代码 提高代
  • elementUI el-button 点击后样式自动恢复的方法

    问题描述 elementUI 中的按钮 点击后会有focus样式 且只有点击其他地方后 按钮才会失焦 样式才会恢复到点击之前的样式 例如主要按钮是亮蓝色 点击后是暗蓝色 点击其他地方才会恢复亮蓝色 我希望将鼠标放在按钮上时会改变样式 但是点
  • 实践指南-前端性能提升 270%

    一 背景 当我们疲于开发一个接一个的需求时 很容易忘记去关注网站的性能 到了某一个节点 猛地发现 随着越来越多代码的堆积 网站变得越来越慢 本文就是从这样的一个背景出发 着手优化网站的前端性能 并总结出一套开发习惯 让我们在日常开发时 也保
  • Web 前端常用正则校验规则

    Web 前端常用正则校验规则 作为 Web 前端开发 常用的正则校验规则有很多 下面是一些常见的示例 1 校验手机号码 手机号码的正则表达式可以根据不同国家和地区的手机号码格式进行调整 以下是中国大陆的手机号码正则表达式 const reg
  • 记一次浏览器下载错误处理-失败网络错误

    背景 最近在自己电脑上Chrome浏览器正常使用 但只要是下载软件 就会在下载几十秒后 自动停止 报失败 网络错误 导致文件都下载不成功 如下图 猜测是更改了哪块的配置 导致一直中断 可以依次检查以下几种方案 1 检查下载文件目录是否存在
  • 在vue3项目中使用新版高德地图

    高德开发平台 高德开放平台 高德地图API amap com 1 首先你要注册好账号登录 2 获取key和密钥 自2021年12月02日升级 升级之后所申请的 key 必须配备安全密钥 jscode 一起使用 NPM方式安装和使用 基础版
  • IDEA中格式化代码快捷键

    一键格式化代碼 Ctrl Alt L 快捷键汇总链接 Intellij IDEA 快捷键整理 TonyCody Eclipse常用快捷键汇总 注意 如果按Ctrl Shift F在win10上会出现字体的简繁转换 再重复按键一次就可以转换回
  • 全网最全HTML基础

    目录 1 HTML结构 2 HTML 文件基本结构 3 HTML常见标签 3 1注释标签 3 2标题标签 h1 h6 3 3 段落标签 p 3 4 换行标签 br 3 5格式化标签 3 6图片标签 img 3 8表格标签 3 9 合并单元格
  • 初识-常见浏览器兼容性问题与解决方案

    浏览器兼容问题一 不同浏览器的标签默认的外补丁和内补丁不同 问题症状 随便写几个标签 不加样式控制的情况下 各自的margin 和padding差异较大 碰到频率 100 解决方案 CSS里 margin 0 padding 0 备注 这个
  • web前端开发自学书籍推荐这5本

    JavaScript权威指南 第6版 淘宝前端团队翻译的 看译者列表都是一堆大神 这本书又叫犀牛书 号称 Javascript 开发者的圣经 网上对此书评价很多 大概意思都是说这本书是一本 JavaScript 文档手册 没有完整看过一遍此
  • 02vue项目如何配置多页面

    vue项目如何配置多页面 前言 因为我们做项目不可能只有一个界面 就简单说最起码的后台页面 登录界面 主页面最基本的加在一起还三个页面 上次所言cli脚手架搭建一个从0到1的项目 只是单页面 cli脚手架虽然能快速的帮我们搭建一个项目 配置
  • 解决多个Tabs频繁切换造成数据错乱问题的方案

    一 利用axios的cancelToken import post from util ajax import axios from axios const CancelToken axios CancelToken post let ca
  • remote: HTTP Basic: Access deniedfatal: Authentication failed for ‘xxxxx‘的问题解决

    在没有修改git密码的情况下 使用vs code推送代码 总是会报错 remote HTTP Basic Access denied fatal Authentication failed for xxxxxxxx git仓库地址 网上试了
  • React WebApp键盘遮挡输入框?

    写在前面 由于近期工作实在太忙 正在赶项目 抽不出时间整理平时遇到的坑 隔了很长时候没有给大家更新文章了 这次正好利用这个单休的时间写一篇开发时遇到的坑 大家如果有什么好的建议和意见欢迎投递哦 邮箱 lcczmy 163 com 本人使用的
  • React 教程及其API接口文档

    React 详细中文开发文档 可以阅读 http reactjs cn react docs tutorial html 英文原文 http facebook github io react 中文论坛 http react china or
  • Volta简单介绍

    Volta是一款强大的JavaScript工具管理器 它简化了命令行工具的安装和管理 通过Volta 开发者可以轻松地在多个项目中切换和配置Node js npm以及其它JavaScript工具版本 提高开发效率和环境一致性 什么是 Vol

随机推荐

  • 【YOLOv5】1.搭建Pycharm+Python+yolov5环境

    目录 一 安装Python 二 安装PyCharm 三 创建项目和虚拟环境 四 下载YOLOv5和依赖库 五 配置Pytorch 六 检验YOLOv5环境 一 安装Python 1 Python官方下载网址 Download Python
  • 解决echarts报错Cannot read properties of null (reading ‘getAttribute‘)

    前言 最近在写 echarts 的时候碰到了这么一个报错 如下图 造成报错的原因是因为 echarts 的图形容器还未生成就对其进行了初始化 下面几种方法是经本人自测最有效的解决方案 报错截图 解决方案 1 this nextTick 该方
  • c++ primer 中的文本查询示例

    前言 有个牛人叫bnu chenshuo 发微博说 回复 TheRealBo 学生编程练习 把 Unix 的命令行小工具用C C 实现一遍 wc cat ls cp grep sort uniq nc head tail hexdump 把
  • 电源正负极防反接保护的几种实现方案!

    电源防反接 应该是很多电路场景下都会采取到此系列得设计 前几日 小白在做单板验证时 在接上假电池然后电源供电时 一不小心将假电池的正负极与供电电源的输入输出接反了 导致单板烧坏 瞬间一缕青烟飘荡在我的座位上 由于我们的产品用的是真电池 所以
  • Ubuntu设置定时重启

    1 安装 更新 cron 安装crontab sudo apt get install cron 更新命令 sudo apt get update 2 配置cron定时任务 sudo nano etc crontab root reboot
  • c语言汇编混合编译不了,是用c语言和汇编混合编的程序,在keil里编译时出现C51 FATAL-ERROR -...

    满意答案 qun260 推荐于 2018 05 13 采纳率 59 等级 8 已帮助 312人 程序问题 LL SEGMENT CODE 在程序存储区中定义段 PUBLIC LED 声明函数 FLAG DATA 20H DPFLAG DAT
  • 几十行代码 轻松实现人脸识别、人脸检测

    人脸识别最近几年变得很火 技术也已经相对成熟 应用场景也很多 下面将介绍简单几种实现人脸检测 人脸识别的简单方法 我博客中也写了几篇有人脸识别应用的文章 现在分类总结下 人脸识别技术介绍已经近况以及应用 https blog csdn ne
  • Java8 利用Lambda处理List集合循环给另外一个List赋值过滤处理

    1 利用stream forEach 循环处理List List
  • mysql查询作为是否有连续以及是否有人

    有张表t1 表结构如图 0 表示座位没人 1表示座位有人 查询出座位是连续的且没有人的seat id SELECT DISTINCT a seat id from t1 as a t1 AS b WHERE a free 1 AND b f
  • golang base64解码编码实现

    golang base64解码编码实现 golang base64解码编码实现 go实现base64解码编码非常简单 知道调用go安装时自带的encoding base64就可以了 package main import encoding
  • ByteHouse 与 Apache Airflow 的数据管理流程

    动手点关注 干货不迷路 Apache Airflow 与 ByteHouse 相结合 为管理和执行数据流程提供了强大而高效的解决方案 本文突出了使用 Apache Airflow 与 ByteHouse 的主要优势和特点 展示如何简化数据工
  • 哈希表冲突及处理冲突的方法(含例子)

    一 哈希函数和哈希冲突的基本概念 1 哈希函数 哈希法又称散列法 杂凑法以及关键字地址计算法等 相应的表成为哈希表 基本思想 首先在元素的关键字K和元素的位置P之间建立一个对应关系f 使得P f K 其中f成为哈希函数 创建哈希表时 把关键
  • Pinpoint--基础--3.1--安装部署--环境准备

    Pinpoint 基础 3 1 安装部署 环境准备 前提 使用hd用户登陆 完成基础环境搭建 https blog csdn net zhou920786312 article details 118212302 代码位置 https gi
  • Java学习笔记

    Java学习笔记 1 知识点 1 0Hello World 1 1Java的枚举 1 2类的构造函数 初始化对象 1 3访问实例变量 调用成员方法 1 4一个源文件只能有一个public类 且与源文件名称保持一致 可以有多个非public类
  • java线上问题排查基本命令

    1 jvm基本命令 1 1 java命令 1 1 1 简介 java命令启动java应用程序 它通过启动Java运行时环境 JRE 加载指定的类并调用该类的main 方法来实现这一点 1 1 2 命令链接 https docs oracle
  • 若依前后端分离框架学习-5:权限管理

    上一章自己创建了一个模块 我们注意到前端代码中对于按钮有v hasPermi xxx testxxx add 这种代码 服务端有 PreAuthorize ss hasPermi xxx testxxx add 这种代码 这是为了分别在前端
  • javax.servlet.ServletException: Could not resolve view with name ‘member/personnelList‘

    问题描述 接口突然就这样了 而且好像还是所有接口都这样了 解决方案 使用 RestController 因为我们要返回Json数据 假如只使用 Controller 这样会导致Springboot去查找视图
  • rabbitmq4.0以下版本遇到断网重连异常

    现象描述 一个客户端反应经常性不更新显示信息 分析是rabbitmq的消息没有及时consumer 登陆服务器控制台查看消息状态是ready 终端连接状态running 但就是不消费信息了 分析 怀疑网络中途有中断波动 1 代码里已经设置s
  • Unity屏幕坐标

    Unity学习笔记 Part 12 一 屏幕坐标 二 屏幕边界 一 屏幕坐标 世界坐标 transform position 该物体在世界空间中的坐标 屏幕坐标 通过屏幕观察 该物体在屏幕上的位置 屏幕坐标即主摄像机观察到的坐标 为Unit
  • React 16 加载性能优化指南

    转自 https zhuanlan zhihu com p 37148975 关于 React 应用加载的优化 其实网上类似的文章已经有太多太多了 随便一搜就是一堆 已经成为了一个老生常谈的问题 但随着 React 16 和 Webpack