Project structure drawing

  1. The development environment uses Webpack-dev-server as the back-end server to achieve hot update without refreshing pages, including hot update of components and reducer changes.
  2. The production environment uses koA as the backend server, and shares the createApp code with the front end. After packaging, the createApp method is obtained by reading the file, and then the react-loadable code is separated as needed, and the initial data is requested before rendering, and loaded into the front page.

Github address: github.com/wd2010/Reac…

The code structure

React + Redux + Router4 uses redux-thunk to process asynchronous actions. The front and back ends share configureStore and createApp, as well as the front end routesConfig configuration required by the back end, so expose all three in one file.

export default {
  configureStore,
  createApp,
  routesConfig
}
Copy the code
Configurestore. js is:
import {createStore, applyMiddleware,compose} from "redux";
import thunkMiddleware from "redux-thunk";
import createHistory from 'history/createMemoryHistory';
import {  routerReducer, routerMiddleware } from 'react-router-redux'
import rootReducer from '.. /store/reducers/index.js';

const routerReducers=routerMiddleware(createHistory());/ / routing
const composeEnhancers = process.env.NODE_ENV=='development'?window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;

const middleware=[thunkMiddleware,routerReducers];

let configureStore=(initialState) = >createStore(rootReducer,initialState,composeEnhancers(applyMiddleware(... middleware)));export default configureStore;

Copy the code

I put the router into the reducer

const routerReducers=routerMiddleware(createHistory());/ / routing
const middleware=[thunkMiddleware,routerReducers];
Copy the code

This allows you to read router information directly from the Reducer without having to pass it down from one layer of the component.

createApp.js
import React from 'react';
import {Provider} from 'react-redux';
import Routers from './router/index';
import Loadable from 'react-loadable';

const createApp=({store,history,modules}) = >{
  if(process.env.NODE_ENV==='production') {return( <Loadable.Capture report={moduleName => modules.push(moduleName)}> <Provider store={store}> <Routers history={history}  /> </Provider> </Loadable.Capture> ) }else{ return ( <Provider store={store}> <Routers history={history} /> </Provider>  ) } } export default createApp;Copy the code

The history used in the front end is:

import createHistory from 'history/createBrowserHistory';let history= createHistory ();Copy the code

The history used at the back end is:

import createHistory from 'history/createMemoryHistory';let history= createHistory ();Copy the code

Development hot load update

if(process.env.NODE_ENV==='development') {if(module.hot){
    module.hot.accept('./store/reducers/index.js', () = > {let newReducer=require('./store/reducers/index.js');
      store.replaceReducer(newReducer)
      /*import('./store/reducers/index.js').then(({default:module})=>{ store.replaceReducer(module) })*/
    })
    module.hot.accept('./app/index.js', () = > {let {createApp}=require('./app/index.js');
      let newReducer=require('./store/reducers/index.js');
      store.replaceReducer(newReducer)
      let application=createApp({store,history});
      hydrate(application,document.getElementById('root'));
      /*import('./app/index.js').then(({default:module})=>{ let {createApp}=module; import('./store/reducers/index.js').then(({default:module})=>{ store.replaceReducer(module) let application=createApp({store,history}); render(application,document.getElementById('root')); })}) * /}}})Copy the code

These include hot and reducer hot updates for components, and require or import can be used to introduce changing files.

Front-end DOM node generation

const renderApp=(a)= >{
  let application=createApp({store,history});
  hydrate(application,document.getElementById('root'));
}

window.main = (a)= > {
  Loadable.preloadReady().then((a)= > {
    renderApp()
  });
};
Copy the code

Loadable.preloadready () is used to load ‘react-loadable’ on demand, which is also used in server rendering.

Router4 is dynamically loaded on demand

This project uses react-loadable to load on demand.

const Loading=(props)=> <div>Loading... </div> const LoadableHome = Loadable({ loader: () =>import(/* webpackChunkName: 'Home' */'.. /.. /containers/Home'), loading: Loading, }); const LoadableUser = Loadable({ loader: () =>import(/* webpackChunkName: 'User' */'.. /.. /containers/User'), loading: Loading, }); const routesConfig=[{ path: '/', exact: true, component: LoadableHome, thunk: homeThunk, }, { path: '/user', component: LoadableUser, thunk: ()=>{}, }];Copy the code

Not only can this be used in routing, but also dynamically import() components. A component can dynamically load components on demand. thunk: HomeThunk is the action handling when the route is redirected, because the first possibility is that the server needs to request the initial data of the Home page before entering the Home page, and the other possibility is that the server enters the user page, and also needs to request the initial data when switching from the user page to the Home page. Now, it’s the front-end component that’s asking for ComponentDidMount, so just to make it common to put this method on a jump route, whether it’s coming in from the front-end link or from the server.

exportConst homeThunk=store=>store.dispatch(getHomeInfo()) // Simulates dynamic request dataexport const getHomeInfo=()=>async(dispatch,getState)=>{
  let {name,age}=getState().homeInfo;
  if(name || age)return
  await new Promise(resolve=>{
    let homeInfo={name:'wd2010',age:'25'}
    console.log('-- -- -- -- -- -- -- -- -- -- - requests getHomeInfo')
    setTimeout(()=>resolve(homeInfo),1000)
  }).then(homeInfo=>{
    dispatch({type:GET_HOME_INFO,data:homeInfo})
  })
}
Copy the code

On the server side, matchRoutes of react-router-config matches the current URL and routing routesConfig

let branch=matchRoutes(routesConfig,ctx.req.url)
let promises = branch.map(({route,match})=>{
    returnroute.thunk? (route.thunk(store)):Promise.resolve(null) }); await Promise.all(promises)Copy the code

Koa rendering renderToString

RenderToString renders the rootString string required by the front-end HTML page through createApp, configureStore, and routesConfig exposed by the front end. Combined with on-demand loading are:

letStore = configureStore ();let history=createHistory({initialEntries:[ctx.req.url]});
let rootString= renderToString(createApp({store,history,modules}));
Copy the code

Use react-loadable on the koA server entry file listening port:

Loadable.preloadAll().then(() => {
  app.listen(port)
})
Copy the code

This allows koA backend rendering to load dynamically on demand.

Dynamically generated HTML does not have user.js:


      
<html>
  <head>
    <meta charset="UTF-8">
    <title>yyy</title>
  <link href="/css/style.7dae77f648cd2652a570.css" rel="stylesheet"></head>
  <body>
    <div id="root"></div>
    <script type="text/javascript" src="/manifest.7dae77f6.js"></script>
    <script type="text/javascript" src="/vendors.7dae77f6.js"></script>
    <script type="text/javascript" src="/client.7dae77f6.js"></script>
  </body>
  <script>window.main()</script>
</html>
Copy the code

At each refresh, localhost already contains all the content of the first screen, solving the first screen blank and SEO search problems.

conclusion

After finishing this exercise, I thought, when the code is compiled, the server will request the data required by the first screen for a short time, and the problem of the white screen is still not solved at this time, so can we request all the data required by the first screen when compiling the code? The compilation process takes a lot of time and requests data that could have been routed at the front end. There is a better solution to all the white-screen problems.

Because it is my first time to render the React server, many places are made by referring to the practice of the big god, and there are a lot of things that do not understand, please give us more advice, the complete code is available at github.com/wd2010/Reac…