抹桥的博客
782 字
4 分钟
React 单页面初始化渲染优化

由于项目使用 React, React-Router, Redux 来开发项目,采取了前端渲染的方式(如果采用 ssr 就不会有这个问题)。

问题#

所以每到一个页面,需要发一个初始化的请求来获取页面数据,然后重新渲染页面,所以之前一个页面从请求到加载完毕的流程是这样的:

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

首先是路由匹配,然后准备加载组件,使用通过 Reducer 里面的初始化 state 进行 render, 然后触发 ComponentDidMount 事件,在这个事件里面去 dispatch 一个执行页面初始化的请求的 Action,请求成功过以后触发组件的重新渲染。

可以看到,展示最终的页面需要组件重新渲染两次。一次是使用写死在前端的reducer 里面的 initialState 数据来渲染,一次是在拿到后端数据进行的渲染。

所以有的时候会出现闪屏的情况,用户体验很不好。

我们的要求是进页面以后就是用从后台获取的最新数据渲染的页面,整个页面只会 render 一次。

那么怎么解决这个问题呢?

解决方案#

要解决这个问题,那么必然是加载好数据后,再去挂载页面组件,那么加载数据的时机就显得尤为重要。

借鉴传统服务端渲染页面的方式,这个时机肯定是放在路由里面去做比较合适。

具体到项目里面,就是在 react-route 里面的 onEnter 事件里面去做页面的初始化请求,当所有数据请求成功以后,在去加载这个页面。

整个页面加载的流程就变成了这样:

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()
React 单页面初始化渲染优化
https://blog.kisnows.com/2017/04/20/react-page-render-improve/
作者
Kisnows
发布于
2017-04-20
许可协议
CC BY-NC-ND 4.0