抹桥的博客
Language
Home
Archive
About
GitHub
Language
主题色
250
877 文字
4 分
React シングルページの初期レンダリング最適化

プロジェクトがReact、React-Router、Reduxを使用して開発されているため、フロントエンドレンダリング方式を採用しました(SSRを採用していればこの問題は発生しませんでした)。

問題#

なので、各ページにアクセスするたびに、ページデータを取得するための初期リクエストを送信し、その後ページを再レンダリングする必要があります。そのため、以前のページのリクエストからロード完了までのフローは次のようになっていました。

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

まずルーティングがマッチし、その後コンポーネントのロード準備が行われます。Reducer 内の初期 state を使用して render が実行され、ComponentDidMount イベントがトリガーされます。このイベント内で、ページ初期化リクエストを実行する Actiondispatch し、リクエストが成功するとコンポーネントの再レンダリングがトリガーされます。

ご覧の通り、最終的なページを表示するにはコンポーネントが2回再レンダリングされる必要があります。1回目はフロントエンドのreducer にハードコードされた initialState データを使用してレンダリングされ、2回目はバックエンドデータが取得された後にレンダリングされます。

そのため、時折画面がちらつくことがあり、ユーザーエクスペリエンスは非常に悪いものでした。

私たちの要件は、ページにアクセスした後、バックエンドから取得した最新データでレンダリングされたページが表示され、ページ全体が1回だけ render されることです。

では、この問題をどのように解決すればよいでしょうか?

解決策#

この問題を解決するには、データをロードしてからページコンポーネントをマウントする必要があります。そのため、データロードのタイミングが非常に重要になります。

従来のサーバーサイドレンダリングページの方法を参考にすると、このタイミングはルーティング内で行うのが最も適切でしょう。

具体的には、プロジェクトでは react-routeronEnter イベント内でページの初期化リクエストを実行し、すべてのデータリクエストが成功した後にこのページをロードします。

ページ全体のロードフローは次のようになります。

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

具体的なコードは以下の通りです。#

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

この記事は 2017年4月20日 に公開され、2017年4月20日 に最終更新されました。3090 日が経過しており、内容が古くなっている可能性があります。

React シングルページの初期レンダリング最適化
https://blog.kisnows.com/ja-JP/2017/04/20/react-page-render-improve/
作者
Kisnows
公開日
2017-04-20
ライセンス
CC BY-NC-ND 4.0