React is designed for large-scale applications. Electron and React-Native have given it the ability to build mobile cross-platform apps and desktop applications. Taro has given it the ability to write and generate multiple platform mini-programs and React-native applications at one time. The document is quite good, and its upgrade speed is relatively fast. I think there are issues that will be solved in time, and their maintenance personnel are very dedicated!

  • Tips: This article some knowledge points if the introduction is not right or incomplete place welcome to point out, this article may be more content, reading time takes a long time, but I hope you can carefully look down, if you can best hand in hand to achieve somecode, all codes in this article are handwritten.

This article will take you from native browser environments to cross-platform development

  • Write React to optimize scaffolding belt projects
  • The source code of the react – SSR
  • Handwritten Node.js native static resource server
  • Cross platform Electron demo

Native browser environment:

  • Native browser environment is actually the most front-end engineer capacity programming environment, because most front end our browser oriented programming at the beginning, now a lot of a lot of work for 5-10 years of front end, performance panel API all don’t know, how can call a function analysis takes all don’t know, this is also a recent interview, feel someone said unemployment situation, 35 years old It’s universal, but a lot of it’s you, man.
Used in native browser environmentsReactFrames are more common for making single pagesSPAApplication:
The nativeSPAApplications are as follows:
  • Pure CSR Rendering (client-side rendering)

  • Pure SSR rendering (server-side rendering)

  • Hybrid rendering (pre-render, webpack plugin pre-render,Next. Js compact route SSR, or use Node.js for middleware, do partial SSR, speed up first screen rendering, or specify route SSR.)

Here’s a closer look at each of these types of refined rendering, as well as the pros and cons:

pureCSRApply colours to a drawing

  • Client requestRestFulInterface, interface spit back static resource file
  • Node.jsThe implementation code
const express = require('express')
const app = express()

app.use(express.static('pulic'App.get (app.get(app.get(app.get(app.get(app.get)))'/',(req,res)=>{
 //do something 
    
})

app.listen(3000,err=>{
    if(! err)=>{ console.log('Listening on port 3000 succeeded')}})Copy the code
  • The client receives an HTML file, several CSS files, and several javaScript files

  • The user enters the URL bar and the client returns the static file, and the client begins parsing

  • The client parses the file and the JS code dynamically generates the page. (This is why SEO for a single page application is not friendly; it starts as an HTML file with an empty DIV tag.)

  • To determine whether a page is a CSR, you can, to a large extent, right click to view page elements. If there is only an empty DIV tag, it is highly likely to be a single page, CSR, and client-rendered web page.

##### Pure CSR application, how to fine render?

Single page takeCSRForm, for the most part, depends on frame,VueandReactAnd so on. Once this type of technical architecture is used, centralized management of state data, one-way data flow, immutable data, lazy loading of routes, loading of components on demand, appropriate caching mechanisms (PWATechnology), finely split components, refreshing components from a single data source, these are all directions we can refine. Often the pureCSRSingle-page applications are generally not too complex, so I won’t introduce them herePWAandweb workWait, I’m going to pile on those technologies later in the complex cross-platform applications.

  • A single data source determining whether a component is refreshed or not is the most important direction for refinement.
class app extends React.PureComponent{

    ///////
}

export default connect(
 (({xx,xxx,xxxx,xxxxx}))
////

)(app)

Copy the code

Once the business logic is very complex, assuming that we use DVA centralized state management and connect so many state tree modules at the same time, arbitrary data refresh in the state tree module may cause the component to be refreshed, but in fact the component does not need to be refreshed at this time.

  • Here, the required state can be passed in through the root component using props to accurately refresh the source. A single variable data source has strong traceability and is more convenient for debugging

  • One-way data flow immutable data is implemented through the immutable. Js library

    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'); / / 50Copy the code

Immutable data, data sharing, persistent storage, through IS comparison, each map is unique, they compare codeHash values, and the performance is much better than through recursion or directly. When shallow PureComponent is not useful

  • For generic components, use PureComponent to reduce repeated rendering

  • PureComponent. The React Component inherits from Component. PureComponent is a purer Component that performs a shallow comparison of the data before and after the update. Components are rerender only if the data has actually changed. This can greatly improve the performance of components.

  • PureComponent is a shallow comparison that evaluates some special values:



function is(x: any, y: any) {
    return( (x === y && (x ! == 0 || 1 / x === 1 / y)) || (x ! == x && y ! == y) ); }Copy the code
It’s important to note here, whyImmutable. Js and pureComponentBecause theReactOnce the root component is brushed

New, will gradually refresh the entire descendant component from top to bottom, so that the performance loss of repeated rendering will be much more, so we not only need to control the component refresh from a single data source, Occasionally you need to compare nextProps with this.props and this.state with nextState in shouldComponentUpdate.

  • Route lazy loading +code-spliting, which speeds up the first screen rendering, also takes the load off the server, because many people may visit your page and not see some of the route content

  • Use react-loadable to support SSR, very recommended, official lazy does not support SSR, this is a pity, we need to use wepback4 optimization configuration, code splitting

The Babel default package @babel/plugin-syntax-dynamic-import supports dynamic import, which supports dynamic import of components

Webpack configuration: Optimization: {runtimeChunk:true,
        splitChunks: {
            chunks: 'all'}}Copy the code
    import React from 'react'
    import Loading from './loading-window'// the placeholder component initially loads import Loadable from'react-loadable'
    const LoadableComponent = Loadable({
        loader: () => import('./sessionWindow'),// True component loading loading: loading,});export default LoadableComponent

Copy the code
  • Ok, now the routing lazy load component and code segmentation are ready, and it supports SSR. Very good

  • Since the web pages of pure CSR are generally not very complex, here is another aspect, that is, the state tree cannot be added without redux, DVA and other centralized state management states. Practice has proved that frequent updating of the state tree has a great impact on user experience. This asynchronous process is more time-consuming. It is better to use props for inter-component communication. In principle, only data shared by many components can be added to the state tree, otherwise, other methods are used for communication.

SSR, server render:

Server-side rendering can be divided into:
Pure server-side rendering, such asjade,tempalte,ejsWait for the template engine to render, and then return to the corresponding front-endHTMLfile
  • It’s also used hereNode. Js + express framework
const express= require('express')
const app =express()
const jade = require('jade') const result = *** const url path = *** const html = jade.renderFile(url, { data: Result, urlPath})// Pass data to template engine app.get('/',(req,res)=>{res.send(HTML)// Directly send the rendered HTML file to the client as a string}) //RestFul interface app.listen(3000,err=>{//do something
})

Copy the code
Mix render, usewebpack4Plugin, prerender the specified route, the specified route isSSRRendering, background 0 code implementation
const PrerenderSPAPlugin = require('prerender-spa-plugin')
new PrerenderSPAPlugin({
            routes: ['/'.'/home'.'/shop'],
            staticDir: resolve(__dirname, '.. /dist'),}),Copy the code
Mix render, useNode.jsAs middleware,SSRSpecifying routes speeds up first screen rendering, of courseCSSCan also be server-side rendering, dynamicTitle and meta tags, betterSEOOptimization, right hereNode.jsData can also be processed simultaneously, reducing the front-end computing burden.
  • I think nuggets on the god three that article is written very well, behind I went to gradually realize a, feel more thorough understanding of SSR, plus was writing Node.js every day, but also a little Next,Nuxt, server-side rendering, feel similar.

  • Server rendering essence, in the server to run the code once, the data will be requested back in advance, return to run the HTML file, the client received the file, pull JS code, code water, and then display, dehydration, JS take over the page.

  • Isomorphic straight out code, can greatly reduce the first screen rendering time, through practice, according to different content and configuration can shorten 40%-65% of the time, but the server rendering will bring pressure to the server, so compromise according to the situation.

  • Here is a simple server rendering, where the server directly spit out the stitched HTML structure string:

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 listen') I}})Copy the code

As long as the client visits localhost:3000, the data page can be accessed

The server renders the core, ensuring that the code runs once on the server, willreduxthestoreThe data in the state tree is returned to the client together, and the client dehydrates and renders. Success is achieved by ensuring that their state data is consistent with the route. Client and server code and data must be consistent, otherwiseSSREven if it fails.
//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(The '*'.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('Connection started'); }); //render function 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';
exportconst render = (req, store) => { const matchedRoutes = matchRoutes(routers, req.path); MatchedRoutes. ForEach (item => {// If the component corresponding to this route has a loadData methodif(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>
    `;
};
Copy the code
  • Data water injection, dehydration, hold client and serverstoreThe consistency of.

The script tag returned above, which has been watered, adds the data obtained on the server to the context property under the global window, which we dehydrate when initializing the client store. Initialize render using data retrieved by the server

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));
};

Copy the code
  • Note that when sending ajax or other data in the component’s componentDidMount lifecycle, check whether there is data in the state tree. If there is data, don’t send requests repeatedly, resulting in a waste of resources.

  • Multi-level SSR routing

// Route configuration file: 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}}]];Copy the code
  • The route part of the entry file was changed to:
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>

Copy the code
  • It may be used laterloaderforCSSServer-side rendering as wellhelmetThe dynamics of themeta, titleLabel forSEOOptimization, etc., time is tight today, will not continue to writeSSR.

buildElectronExtremely complex, very large data applications.

It takes technology,Sqlite,PWA,web work, native node. js,react-window, react-lazyload, C++ plugins, etc
  • The first mentioned is SQLite, an embedded relational database, a lightweight, non-invasive, standard SQL statement that I won’t cover too much here.

  • PWA, progressive Web application, which uses webPack4 plug-in for quick use, can be used for some data content that does not need to store database, but want to pull once and reuse many times

Serverce Work also has its own life cycle

  • Generally, the following steps are used to use a Service Worker:

  • First we need to use serviceWorkerContainer in JavaScript the main thread of the page. The register () to register the Service Worker, in the process of registration, The browser initiates the installation steps of the Service Worker in the background.

  • If the registration is successful, the ServiceWorker runs in the ServiceWorkerGlobalScope environment. This is a special worker context that is independent of the main script’s running thread and has no DOM access.

  • The background starts the installation process, usually during the installation process to cache some static resources. If all resources are cached successfully, the installation succeeds; if any static resource cache fails, the installation fails. It does not matter if the installation fails, the installation will continue automatically until the installation succeeds. If the installation fails, the next step – activating the Service Worker cannot be proceeded.

  • To activate the Service Worker, you must install the Service Worker successfully before starting the activation step. When the installation is complete, an activation event will be received. In the activation event handler, the main action is to clean up the resources used in the old version of the Service Worker script.

  • After successfully activating the Service Worker can control pages, but only for pages opened after successfully registering the Service Worker. That is, whether a page is opened with or without a Service Worker determines whether the page is controlled by the Service Worker during its life cycle. Therefore, pages that were not previously controlled by the Service Worker can be controlled only when the page is refreshed.

Directly to the code, store all js files and images // actual storage according to their own needs, not the more the better.

const WorkboxPlugin = require('workbox-webpack-plugin')
new WorkboxPlugin.GenerateSW({
            clientsClaim: true,
            skipWaiting: true,
            importWorkboxFrom: 'local',
            include: [/\.js$/, /\.css$/, /\.html$/, /\.jpg/, /\.jpeg/, /\.svg/, /\.webp/, /\.png/],
        }),
Copy the code
  • PWA is not only these features, it is very powerful, you can go to Lavas to have a look,PWA technology for regular customers to visit the first screen rendering is very large, especially on the mobile end, can be added to the desktop save. 666 ah ~, on the PC side is more cache processing files ~

  • Use react-lazyLoad to lazily load components or images that are initially invisible to your viewport.

/ out of the box LazyLoad image 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> Remember to call forceCheck on the mobile or PC side to dynamically calculate the element's position from the window and decide whether to display the real image ~ import {forceCheck} from'react-lazyload';
forceCheck()

Copy the code
  • Lazily loaded component
import { lazyload } from 'react-lazyload'; // This is just a decorator, a higher order function. Forcecheck () @lazyload({height: 200, once:true,
  offset: 100
})
class MyComponent extends React.Component {
  render() {
    return <div>this component is lazyloaded by default!</div>;
  }
}

Copy the code

Big dataReactRender, own let the application have 60FPS– A very core bit of optimization

  • ListA long list of

  • React-virtualized – Auto-Sizer and windowScroll were used together to achieve complex graphics and maintain 60FPS with big data. These components are described on the website above

High computational workload to handweb wrokthread


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');
}
Copy the code
  • In this code, the variables first and second represent two input elements; When either of these values changes, myworker.postMessage ([first.value,second.value]) sends the two values as an array to the worker. You can send as much as you want in a message.

  • After receiving a message in the worker, we can write an event handler (worker.js) as a response:

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);
}
Copy the code
  • The onMessage handler allows us to execute code at any time once a message is received, using the message itself as the data property of the event. Here we simply multiply the two numbers and pass the result back to the main thread using the postMessage() method again.

  • Back to the main thread, we use onMessage again in response to the message returned by the worker:

myWorker.onmessage = function(e) {
  result.textContent = e.data;
  console.log('Message received from worker');
}
Copy the code
  • Here we get the data of the message event and set it to the textContent of Result, so the user can see the result of the operation directly.

  • Note: OnMessage and postMessage() must hang on worker objects when used in the main thread, but not when used in workers. The reason is that, within workers, workers are valid global scopes.

  • Note: When a message is passed between the main thread and the worker, it is copied or transferred, not shared.

In fact, node.js and javaScript are not suitable for doing a lot of computing work. This is obvious, especially when the JS engine and GUI rendering thread are mutually exclusive.

Make full use ofReacttheFeberarchitecturediffAlgorithm optimization project

  • requestAnimationFrameCall the high-priority task, interrupt the traversal of the scheduling phase, becauseReactThe new version of the scheduling stage is the interruptible list traversal with three Pointers, so this does not affect the following traversal, nor does it affect user interaction and other behaviors.

RequestAnimationFrame is also a better way to keep the browser animated at 60 frames

  • With requestAnimationFrame, while the page is inactive, the screen refresh task for that page is paused by the system, as is requestAnimationFrame, which keeps the screen refresh execution synchronized. When the page is activated, the animation picks up where it left off, saving CPU overhead.

  • It does not make sense if the function is executed more than once in a refresh interval, because the monitor is refreshed every 16.7ms and multiple draws do not show up on the screen

  • In high frequency events (resize, Scroll, etc.), using requestAnimationFrame prevents multiple function executions within a refresh interval, thus ensuring fluency. In some cases, you can simply use requestAnimationFrame instead of Throttle to limit the frequency of callback execution

  • RequestIdleCallback, this API is not very compatible at present, but in Electron development, it can be used, there are differences between the two, and these two apis can be used to solve many complex problems ~. Of course, you can also wrap this API with the above API, which is not very complicated.

  • RequestIdleCallback should be used when you are concerned about the user experience and don’t want users to feel stuck because of unimportant tasks such as statistics reporting. Because the requestIdleCallback callback is executed only if the current browser is idle.

  • A frame contains user interaction, JS execution, requestAnimationFrame calls, layout calculations, and page redrawing. If a frame has a small number of tasks to perform, and the task is completed in less than 16ms (1000/60), then the frame has a certain amount of free time, which is exactly the time to execute the requestIdleCallback callback, as shown in the following figure:

usepreload.prefetch.dns-prefetchThe specified file is requested in advance, or, depending on the case, the browser decides whether to do sodnsPreparse or request certain resources on demand.

  • I can do it herewebpack4Plug-in implementation, jingdong is currently using this program ~
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']}),Copy the code

To specifyjsFile lazy loading ~

  • toscriptLabel, add toasyncTag, which is requested first but does not block parsinghtmlWhen the file comes back, the request will be loaded immediately

  • toscriptLabel, add todeferTag, lazily loaded, but not loaded until all scripts have loaded, but this tag doesbug, not sure whether it can be loaded on time. Usually only one is given

It takes too long to write this article, react-native and some details will be added later

Here are some source codes and information addresses:

  • Write React to optimize scaffolding belt projects

  • The source code of the react – SSR

  • Handwritten Node.js native static resource server

  • Cross platform Electron demo