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/