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
的以及一些細節,後面再補充