React
为了大型应用而生,Electron
和React-native
赋予了它构建移动端跨平台App
和桌面应用的能力,Taro
则赋予了它一次编写,生成多种平台小程序和React-native
应用的能力,这里特地说下Taro
,它是国产,文档写得比较不错,并且它的升级速度比较快,有issue
我看也会及时解决,他们的维护人员仍是很是敬业的!css
Tips
:本文某些知识点若是介绍不对或者不全的地方欢迎指出,本文可能内容比较多,阅读时间花费比较长,可是但愿你能够认真看下去,能够的话最好手把手去实现一些code
,本文全部代码均手写。React
框架,比较常见的是制做单页面SPA
应用:SPA
应用,分如下几种:纯CSR
渲染(客户端渲染)html
纯SSR
渲染(服务端渲染)前端
混合渲染(预渲染,webpack
的插件预渲染,Next.js
的约定式路由SSR
,或者使用Node.js
作中间件,作部分SSR
,加快首屏渲染,或者指定路由SSR
.)java
CSR
渲染RestFul
接口,接口吐回静态资源文件Node.js
实现代码const express = require('express')
const app = express()
app.use(express.static('pulic'))//这里的public就是静态资源的文件夹,让客户端拉取的,这里的代码是前端的代码已经构建完毕的代码
app.get('/',(req,res)=>{
//do something
})
app.listen(3000,err=>{
if(!err)=>{
console.log('监听端口号3000成功')
}
})
复制代码
客户端收到一个HTML
文件,和若干个CSS
文件,以及多个javaScript
文件react
用户输入了url
地址栏而后客户端返回静态文件,客户端开始解析webpack
客户端解析文件,js
代码动态生成页面。(这也是为何说单页面应用的SEO
不友好的缘由,初始它只是一个空的div
标签的HTML
文件)git
判断一个页面是否是CSR
,很大程度上能够根据右键点开查看页面元素,若是只有一个空的div
标签,那么大几率能够说是单页面,CSR
,客户端渲染的网页。github
#####纯CSR
的应用,如何精细化渲染呢?web
CSR
形式,大都依赖框架,Vue
和React
之类。一旦使用这类型技术架构,状态数据集中管理,单向数据流,不可变数据,路由懒加载,按需加载组件,适当的缓存机制(PWA
技术),细致拆分组件,单一数据来源刷新组件,这些都是咱们能够精细化的方向。每每纯CSR
的单页面应用通常不会太复杂,因此这里不引入PWA
和web work
等等,在后面复杂的跨平台应用中我会将那些技术蜂拥而上。class app extends React.PureComponent{
///////
}
export default connect(
(({xx,xxx,xxxx,xxxxx}))
////
)(app)
复制代码
一旦业务逻辑很是复杂的状况下,假设咱们使用的是
dva
集中状态管理,同时链接这么多的状态树模块,那么可能会形成状态树模块中任意的数据刷新致使这个组件被刷新,可是其实这个组件此时是不须要刷新的。面试
这里能够将须要的状态经过根组件用props
传入,精确刷新的来源,单一可变数据来源追溯性强,也更方便debug
单向数据流不可变数据,经过immutable.js
这个库实现
import Immutable from require('immutable');
var map1: Immutable.Map<string, number>;
map1 = Immutable.Map({ a: 1, b: 2, c: 3 });
var map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50
复制代码
不可变数据,数据共享,持久化存储,经过
is
比较,每次map
生成的都是惟一的 ,它们比较的是codehash
的值,性能比经过递归或者直接比较强不少。在PureComponent
浅比较很差用的时候
通常的组件,使用PureComponent
减小重复渲染便可
PureComponent,平时咱们建立 React 组件通常是继承于 Component,而 PureComponent 至关因而一个更纯净的 Component,对更新先后的数据进行了一次浅比较。只有在数据真正发生改变时,才会对组件从新进行 render。所以能够大大提升组件的性能。
PureComponent
部分源码,其实就是浅比较,只不过对一些特殊值进行了判断:
function is(x: any, y: any) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y)
);
}
复制代码
immutable.js和pureComponent
,由于React
一旦根组件被刷新,会自上而下逐渐刷新整个子孙组件,这样性能损耗重复渲染就会多出不少,因此咱们不只要单一数据来源控制组件刷新,偶尔还须要在shouldComponentUpdate
中对比nextProps和this.props
以及this.state以及nextState
.
路由懒加载+code-spliting
,加快首屏渲染,也能够减轻服务器压力,由于不少人可能访问你的网页并不会看某些路由的内容
使用react-loadable
,支持SSR
,很是推荐,官方的lazy
不支持SSR
,这是一个遗憾,这里须要配合wepback4
的optimization
配置,进行代码分割
Tips:
这里须要下载支持动态import
的babel预设包 @babel/plugin-syntax-dynamic-import
,它支持动态倒入组件
webpack配置:
optimization: {
runtimeChunk: true,
splitChunks: {
chunks: 'all'
}
}
复制代码
import React from 'react'
import Loading from './loading-window'//占位的那个组件,初始加载
import Loadable from 'react-loadable'
const LoadableComponent = Loadable({
loader: () => import('./sessionWindow'),//真正须要加载的组件
loading: Loading,
});
export default LoadableComponent
复制代码
好了,如今路由懒加载组件以及代码分割已经作好了,并且它支持SSR
。很是棒
因为纯CSR
的网页通常不是很复杂,这里再介绍一个方面,那就是,能不用redux,dva
等集中状态管理的状态就不上状态树,实践证实,频繁更新状态树对用户体验来讲是影响很是大的。这个异步的过程,更耗时。远不如支持经过props
等方式进行组件间通讯,原则上除了不少组件共享的数据才上状态树,不然都采用其余方式进行通讯。
SSR
,服务端渲染:jade,tempalte,ejs
等模板引擎进行渲染,而后返回给前端对应的HTML
文件Node.js+express框架
const express= require('express')
const app =express()
const jade = require('jade')
const result = ***
const url path = ***
const html = jade.renderFile(url, { data: result, urlPath })//传入数据给模板引擎
app.get('/',(req,res)=>{
res.send(html)//直接吐渲染好的`html`文件拼接成字符串返回给客户端
}) //RestFul接口
app.listen(3000,err=>{
//do something
})
复制代码
webpack4
插件,预渲染指定路由,被指定的路由为SSR
渲染,后台0代码实现const PrerenderSPAPlugin = require('prerender-spa-plugin')
new PrerenderSPAPlugin({
routes: ['/','/home','/shop'],
staticDir: resolve(__dirname, '../dist'),
}),
复制代码
Node.js
做为中间件,SSR
指定的路由加快首屏渲染,固然CSS
也能够服务端渲染,动态Title和meta标签
,更好的SEO
优化,这里Node.js
还能够同时处理数据,减轻前端的计算负担。我以为掘金上的神三元那篇文章就写得很好,后面我本身去逐步实现了一次,感受对SSR
对理解更为透彻,加上原本就天天在写Node.js
,还会一点Next,Nuxt
,服务端渲染,以为大同小异。
服务端渲染本质,在服务端把代码运行一次,将数据提早请求回来,返回运行后的html
文件,客户端接到文件后,拉取js
代码,代码注水,而后显示,脱水,js
接管页面。
同构直出代码,能够大大下降首屏渲染时间,通过实践,根据不一样的内容和配置能够缩短40%-65%时间,可是服务端渲染会给服务器带来压力,因此折中根据状况使用。
如下是一个最简单的服务端渲染,服务端直接吐拼接后的html
结构字符串:
var express = require('express')
var app = express()
app.get('/', (req, res) => {
res.send(
`
<html>
<head>
<title>hello</title>
</head>
<body>
<h1>hello world </h1>
</body>
</html>
`
)
})
app.listen(3000, () => {
if(!err)=>{
console.log('3000监听')Ï
}
})
复制代码
只要客户端访问
localhost:3000
就能够拿到数据页面访问
redux
的store
状态树中的数据一块儿返回给客户端,客户端脱水,渲染。 保证它们的状态数据和路由一致,就能够说是成功了。必需要客户端和服务端代码和数据一致性,不然SSR
就算失败。//server.js
// server/index.js
import express from 'express';
import { render } from '../utils';
import { serverStore } from '../containers/redux-file/store';
const app = express();
app.use(express.static('public'));
app.get('*', function(req, res) {
if (req.path === '/favicon.ico') {
res.send();
return;
}
const store = serverStore();
res.send(render(req, store));
});
const server = app.listen(3000, () => {
var host = server.address().address;
var port = server.address().port;
console.log(host, port);
console.log('启动链接了');
});
//render函数
import Routes from '../Router';
import { renderToString } from 'react-dom/server';
import { StaticRouter, Link, Route } from 'react-router-dom';
import React from 'react';
import { Provider } from 'react-redux';
import { renderRoutes } from 'react-router-config';
import routers from '../Router';
import { matchRoutes } from 'react-router-config';
export const render = (req, store) => {
const matchedRoutes = matchRoutes(routers, req.path);
matchedRoutes.forEach(item => {
//若是这个路由对应的组件有loadData方法
if (item.route.loadData) {
item.route.loadData(store);
}
});
console.log(store.getState(),Date.now())
const content = renderToString(
<Provider store={store}>
<StaticRouter location={req.path}>{renderRoutes(routers)}</StaticRouter>
</Provider>
);
return `
<html>
<head>
<title>ssr123</title>
</head>
<body>
<div id="root">${content}</div>
<script>window.context={state:${JSON.stringify(store.getState())}}</script>
<script src="/index.js"></script>
</body>
</html>
`;
};
复制代码
store
的一致性。上面返回的
script
标签,里面已经注水,将在服务端获取到的数据给到了全局window下的context属性,在初始化客户端store
时候咱们给它脱水。初始化渲染使用服务端获取的数据~
import thunk from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
export const getClientStore = () => {
const defaultState = window.context ? window.context.state : {};
return createStore(reducers, defaultState, applyMiddleware(thunk));
};
export const serverStore = () => {
return createStore(reducers, applyMiddleware(thunk));
};
复制代码
这里注意,在组件的componentDidMount
生命周期中发送ajax
等获取数据时候,先判断下状态树中有没有数据,若是有数据,那么就不要重复发送请求,致使资源浪费。
多层级路由SSR
//路由配置文件,改为这种方式
import Home from './containers/Home';
import Login from './containers/Login';
import App from './containers/app';
export default [
{
component: App,
routes: [
{
path: '/',
component: Home,
exact: true,
loadData: Home.loadData
},
{
path: '/login',
component: Login,
exact: true
}
]
}
];
复制代码
server.js
const content = renderToString(
<Provider store={store}>
<StaticRouter location={req.path}>{renderRoutes(routers)}</StaticRouter>
</Provider>
);
client.js
<Provider store={store}>
<BrowserRouter>{renderRoutes(routers)}</BrowserRouter>
</Provider>
复制代码
loader
进行CSS
的服务端渲染以及helmet
的动态meta, title
标签进行SEO
优化等,今天时间紧促,就不继续写SSR
了。Electron
极度复杂,超大数据的应用。sqlite,PWA,web work,原生Node.js,react-window,react-lazyload,C++插件等
第一个提到的是sqlite
,嵌入式关系型数据库,轻量型无入侵性,标准的sql
语句,这里不作过多介绍。
PWA
,渐进性式web应用,这里使用webpack4
的插件,进行快速使用,对于一些数据内容不须要存储数据库的,可是却想要一次拉取,屡次复用,那么可使用这个配置
一般咱们若是要使用 Service Worker 基本就是如下几个步骤:
首先咱们须要在页面的 JavaScript 主线程中使用 serviceWorkerContainer.register() 来注册 Service Worker ,在注册的过程当中,浏览器会在后台启动尝试 Service Worker 的安装步骤。
若是注册成功,Service Worker 在 ServiceWorkerGlobalScope 环境中运行; 这是一个特殊的 worker context,与主脚本的运行线程相独立,同时也没有访问 DOM 的能力。
后台开始安装步骤, 一般在安装的过程当中须要缓存一些静态资源。若是全部的资源成功缓存则安装成功,若是有任何静态资源缓存失败则安装失败,在这里失败的没关系,会自动继续安装直到安装成功,若是安装不成功没法进行下一步 — 激活 Service Worker。
开始激活 Service Worker,必需要在 Service Worker 安装成功以后,才能开始激活步骤,当 Service Worker 安装完成后,会接收到一个激活事件(activate event)。激活事件的处理函数中,主要操做是清理旧版本的 Service Worker 脚本中使用资源。
激活成功后 Service Worker 能够控制页面了,可是只针对在成功注册了 Service Worker 后打开的页面。也就是说,页面打开时有没有 Service Worker,决定了接下来页面的生命周期内受不受 Service Worker 控制。因此,只有当页面刷新后,以前不受 Service Worker 控制的页面才有可能被控制起来。
直接上代码,存储全部
js文件和图片
//实际的存储根据自身须要,并非越多越好。
const WorkboxPlugin = require('workbox-webpack-plugin')
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true,
importWorkboxFrom: 'local',
include: [/\.js$/, /\.css$/, /\.html$/, /\.jpg/, /\.jpeg/, /\.svg/, /\.webp/, /\.png/],
}),
复制代码
PWA
并不只仅这些功能,它的功能很是强大,有兴趣的能够去lavas
看看,PWA
技术对于常常访问的老客户来讲,首屏渲染提高很是大,特别在移动端,能够添加到桌面保存。666啊~,在pc
端更多的是缓存处理文件~
使用react-lazyload
,懒加载你的视窗初始看不见的组件或者图片。
/开箱即用的懒加载图片
import LazyLoad from 'react-lazyload'
<LazyLoad height={42} offset={100} once> //这里配置表示占位符的样式~。
<img
src={this.state.src}
onError={this.handleError.bind(this)}
className={className || 'avatar'}
/>
</LazyLoad>
记得在移动端的滑动屏幕或者PC端的调用forceCheck,动态计算元素距离视窗的位置而后决定是否显示真的图片~
import { forceCheck } from 'react-lazyload';
forceCheck()
复制代码
import { lazyload } from 'react-lazyload';
//跟上面同理,不过是一个装饰器,高阶函数而已。同样须要forcecheck()
@lazyload({
height: 200,
once: true,
offset: 100
})
class MyComponent extends React.Component {
render() {
return <div>this component is lazyloaded by default!</div>;
}
}
复制代码
React
渲染,拥有让应用拥有60FPS
-很是核心的一点优化List
长列表web wrok
线程var myWorker = new Worker('worker.js');
first.onchange = function() {
myWorker.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}
second.onchange = function() {
myWorker.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}
复制代码
这段代码中变量first和second表明2个input元素;它们当中任意一个的值发生改变时,myWorker.postMessage([first.value,second.value])会将这2个值组成数组发送给worker。你能够在消息中发送许多你想发送的东西。
在worker中接收到消息后,咱们能够写这样一个事件处理函数代码做为响应(worker.js):
onmessage = function(e) {
console.log('Message received from main script');
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
console.log('Posting message back to main script');
postMessage(workerResult);
}
复制代码
onmessage处理函数容许咱们在任什么时候刻,一旦接收到消息就能够执行一些代码,代码中消息自己做为事件的data属性进行使用。这里咱们简单的对这2个数字做乘法处理并再次使用postMessage()方法,将结果回传给主线程。
回到主线程,咱们再次使用onmessage以响应worker回传的消息:
myWorker.onmessage = function(e) {
result.textContent = e.data;
console.log('Message received from worker');
}
复制代码
在这里咱们获取消息事件的data,而且将它设置为result的textContent,因此用户能够直接看到运算的结果。
注意: 在主线程中使用时,onmessage和postMessage() 必须挂在worker对象上,而在worker中使用时不用这样作。缘由是,在worker内部,worker是有效的全局做用域。
注意: 当一个消息在主线程和worker之间传递时,它被复制或者转移了,而不是共享。
开启
web work
线程,其实也会损耗必定的主线程的性能,可是大量计算的工做交给它也何尝不可,其实Node.js
和javaScript
都不适合作大量计算工做,这点有目共睹,尤为是js
引擎和GUI
渲染线程互斥的状况存在。
React
的Feber
架构diff
算法优化项目requestAnimationFrame
调用高优先级任务,中断调度阶段的遍历,因为React
的新版本调度阶段是拥有三根指针的可中断的链表遍历,因此这样既不影响下面的遍历,也不影响用户交互等行为。使用
requestAnimationFrame
也能够更好的让浏览器保持60帧的动画
使用requestAnimationFrame,当页面处于未激活的状态下,该页面的屏幕刷新任务会被系统暂停,因为requestAnimationFrame保持和屏幕刷新同步执行,因此也会被暂停。当页面被激活时,动画从上次停留的地方继续执行,节约 CPU 开销。
一个刷新间隔内函数执行屡次时没有意义的,由于显示器每 16.7ms 刷新一次,屡次绘制并不会在屏幕上体现出来
在高频事件(resize,scroll等)中,使用requestAnimationFrame能够防止在一个刷新间隔内发生屡次函数执行,这样保证了流畅性,也节省了函数执行的开销 某些状况下能够直接使用requestAnimationFrame替代 Throttle 函数,都是限制回调函数执行的频率
requestIdleCallback
,这个API
目前兼容性不太好,可是在Electron
开发中,可使用,二者仍是有区别的,并且这两个api
用好了能够解决不少复杂状况下的问题~。固然你也能够用上面的api
封装这个api
,也并非很复杂。
当关注用户体验,不但愿由于一些不重要的任务(如统计上报)致使用户感受到卡顿的话,就应该考虑使用requestIdleCallback。由于requestIdleCallback回调的执行的前提条件是当前浏览器处于空闲状态。
preload
,prefetch
,dns-prefetch
等指定提早请求指定文件,或者根据状况,浏览器自行决定是否提早dns
预解析或者按需请求某些资源。webpack4
插件实现,目前京东在使用这个方案~const PreloadWebpackPlugin = require('preload-webpack-plugin')
new PreloadWebpackPlugin({
rel: 'preload',
as(entry) {
if (/\.css$/.test(entry)) return 'style';
if (/\.woff$/.test(entry)) return 'font';
if (/\.png$/.test(entry)) return 'image';
return 'script';
},
include:'allChunks'
//include: ['app']
}),
复制代码
js
文件延迟加载~script
标签,加上async
标签,遇到此标签,先去请求,可是不阻塞解析html
等文件~,请求回来就立马加载script
标签,加上defer
标签,延迟加载,可是必须在全部脚本加载完毕后才会加载它,可是这个标签有bug
,不肯定可否准时加载。通常只给一个写这篇时间太耗时间,
React-native
的以及一些细节,后面再补充