抹桥的博客
Language
Home
Archive
About
GitHub
Language
主题色
250
593 words
3 minutes
Optimizing React SPA Initial Rendering

Since the project uses React, React-Router, and Redux for development, it adopts a client-side rendering approach (this issue wouldn’t exist if SSR were used).

Problem#

Therefore, each time a page is accessed, an initial request needs to be sent to fetch page data, and then the page is re-rendered. So, the previous flow from request to page load was like this:

Route match -> ComponentWillMount ->  render -> ComponentDidMount -> dispatch(init())->  render -> componentDidUpdate

First, route matching occurs. Then, the component is prepared for loading. It renders using the initial state from the Reducer. Then, the ComponentDidMount event is triggered. Within this event, an Action is dispatched to execute the page’s initialization request. Upon successful request, the component is re-rendered.

As you can see, displaying the final page requires the component to render twice. One render uses the initialState data hardcoded in the frontend reducer, and the other occurs after receiving data from the backend.

This sometimes leads to a flickering screen effect, resulting in a poor user experience.

Our requirement is that when entering a page, it should be rendered directly with the latest data fetched from the backend, and the entire page should only render once.

So, how can we solve this problem?

Solution#

To solve this problem, it’s essential to load the data first, and then mount the page component. Therefore, the timing of data loading becomes crucial.

Drawing inspiration from traditional server-side rendering methods, the most suitable time for this is within the router.

Specifically for our project, this means making the page’s initial request within the onEnter event of react-router. Once all data requests are successful, the page is then loaded.

The entire page loading process then becomes like this:

Route match -> dispatch(init()) -> ComponentWillMount ->  render -> ComponentDidMount

Specific Code Examples:#

HomeAction.js

import { createAction } from "redux-actions";

import { HOME_INDEX } from "../../config/apis.js";
import createAsyncAction from "../../utils/createAsyncAction.js";
import initAPI from "../../utils/initAPI.js";

export const InitActionList = createAsyncAction("home/init");
export const FormChange = "home/formChange";
export const FormFieldChange = "home/formFieldChange";

export function init() {
  return initAPI(InitActionList, HOME_INDEX, "get");
}

HomeReducer.js

import {
  BillStatus,
  CreditStatus,
  InstallmentStatus,
} from "../../config/constant";
import { InitActionList } from "./HomeAction.js";

const initState = {
  foo: 1,
  bar: 10,
};

export default function (state = initState, action) {
  const type = action.type;
  const payload = action.payload;
  const meta = action.meta;
  switch (type) {
    case InitActionList.start:
      return state;
    case InitActionList.success:
      const currentStatus = getCurrentStatus(payload);
      return {
        ...state,
        foo: currentStatus,
      };
    case InitActionList.failure:
      return state;
    default:
      return state;
  }
}

Router.js

import { init as initHome } from "./homeAction";

export default function createRoutes(store) {
  function initHome(store) {
    return (nextState, replace, next) => {
      // dispatch 页面加载的 Action,在数据加载完成后在执行 next() 以挂载组件
      store.dispatch(homeInit()).then(() => next());
    };
  }

  return {
    component: App,
    path: "/",
    childRoutes: [
      require("./activate"),
      {
        path: "test",
        getComponent(nextState, cb) {
          require.ensure(
            [],
            (require) => {
              cb(null, require("../views/Test").default);
            },
            "Test",
          );
        },
      },
    ],
    indexRoute: {
      getComponent(nextState, cb) {
        require.ensure(
          [],
          (require) => {
            cb(null, require("../views/Home").default);
          },
          "Home",
        );
      },
      onEnter: initHome(store),
    },
  };
}

Index.js

import React from "react";
import { render } from "react-dom";
import { AppContainer } from "react-hot-loader";
import { Provider } from "react-redux";
import { hashHistory, Router } from "react-router";
import { syncHistoryWithStore } from "react-router-redux";

import "react-hot-loader/patch";

import createRoutes from "./routes";
import configureStore from "./store";

import "./style/app.scss";

export const store = configureStore(hashHistory, {});
const history = syncHistoryWithStore(hashHistory, store);
const root = document.getElementById("root");
const routers = createRoutes(store);

const renderRoot = () => {
  render(
    <AppContainer>
      <Provider store={store} key="provider">
        <Router routes={routers} history={history} key={Math.random()} />
      </Provider>
    </AppContainer>,
    root,
  );
};

if (module.hot) {
  module.hot.accept("./routes", () => {
    renderRoot();
  });
}

renderRoot();

This article was published on April 20, 2017 and last updated on April 20, 2017, 3090 days ago. The content may be outdated.

Optimizing React SPA Initial Rendering
https://blog.kisnows.com/en-US/2017/04/20/react-page-render-improve/
Author
Kisnows
Published at
2017-04-20
License
CC BY-NC-ND 4.0