React
为了大型应用而生,Electron
和React-native
赋予了它构建移动端跨平台App
和桌面应用的能力,Taro
则赋予了它一次编写,生成多种平台小程序和React-native
应用的能力,这里特地说下Taro
,它是国产,文档写得比较不错,并且它的升级速度比较快,有issue
我看也会及时解决,他们的维护人员仍是很是敬业的!
,
css
Tips
:本文某些知识点若是介绍不对或者不全的地方欢迎指出,本文可能内容比较多,阅读时间花费比较长,可是但愿你能够认真看下去,能够的话最好手把手去实现一些code
,本文全部代码均手写。React
框架,比较常见的是制做单页面SPA
应用:SPA
应用,分如下几种:CSR
渲染(客户端渲染)SSR
渲染(服务端渲染)webpack
的插件预渲染,Next.js
的约定式路由SSR
,或者使用Node.js
作中间件,作部分SSR
,加快首屏渲染,或者指定路由SSR
.)CSR
渲染客户端请求RestFul
接口,接口吐回静态资源文件html
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
文件url
地址栏而后客户端返回静态文件,客户端开始解析js
代码动态生成页面。(这也是为何说单页面应用的SEO
不友好的缘由,初始它只是一个空的div
标签的HTML
文件)CSR
,很大程度上能够根据右键点开查看页面元素,若是只有一个空的div
标签,那么大几率能够说是单页面,CSR
,客户端渲染的网页。CSR
的应用,如何精细化渲染呢?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
部分源码,其实就是浅比较,只不过对一些特殊值进行了判断: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
接管页面。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
的插件,进行快速使用,对于一些数据内容不须要存储数据库的,可是却想要一次拉取,屡次复用,那么可使用这个配置
直接上代码,存储全部
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
长列表
]java
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'); }
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); }
myWorker.onmessage = function(e) { result.textContent = e.data; console.log('Message received from worker'); }
开启web work
线程,其实也会损耗必定的主线程的性能,可是大量计算的工做交给它也何尝不可,其实Node.js
和javaScript
都不适合作大量计算工做,这点有目共睹,尤为是js
引擎和GUI
渲染线程互斥的状况存在。
React
的Feber
架构diff
算法优化项目requestAnimationFrame
调用高优先级任务,中断调度阶段的遍历,因为React
的新版本调度阶段是拥有三根指针的可中断的链表遍历,因此这样既不影响下面的遍历,也不影响用户交互等行为。
某些状况下能够直接使用requestAnimationFrame替代 Throttle 函数,都是限制回调函数执行的频率react
使用
requestAnimationFrame
也能够更好的让浏览器保持60帧的动画
requestIdleCallback
,这个API
目前兼容性不太好,可是在Electron
开发中,可使用,二者仍是有区别的,并且这两个api
用好了能够解决不少复杂状况下的问题~。固然你也能够用上面的api
封装这个api
,也并非很复杂。
假如某一帧里面要执行的任务很少,在不到16ms(1000/60)的时间内就完成了上述任务的话,那么这一帧就会有必定的空闲时间,这段时间就刚好能够用来执行requestIdleCallback的回调,以下图所示:webpack
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
的以及一些细节,后面再补充