详解React服务端渲染从入门到精通(3)

经过上边的过程,我们已经可以从window.context中拿到服务端预获取的数据了,此时需要做的事就是用这份数据去初始化浏览器端的store。保证两端数据的统一。

import { createStore, applyMiddleware, compose } from 'redux' import thunk from 'redux-thunk' import rootReducer from '../reducers' const defaultStore = window.context && window.context.INITIAL_STATE const clientStore = createStore( rootReducer, defaultStore,// 利用服务端的数据初始化浏览器端的store compose( applyMiddleware(thunk), window.devToolsExtension ? window.devToolsExtension() : f=>f ) )

至此,服务端渲染的数据统一问题就解决了,再来回顾一下整个流程:

用户访问路由,服务端根据路由匹配出对应路由内的组件数组

循环数组,调用组件上挂载的loadData方法,发送请求,扩充服务端store

所有请求完成后,通过store.getState,获取到服务端预获取的数据,注入到window.context中

浏览器渲染返回的HTML,加载浏览器端js,从window.context中取数据来初始化浏览器端的store,渲染组件

这里还有个点,也就是当我们从路由进入到其他页面的时候,组件内的loadData方法并不会执行,它只会在刷新,服务端渲染路由的时候执行。

这时候会没有数据。所以我们还需要在componentDidMount中去发请求,来解决这个问题。因为componentDidMount不会在服务端渲染执行,所以不用担心请求重复发送。

样式的服务端渲染

以上我们所做的事情只是让网页的内容经过了服务端的渲染,但是样式要在浏览器加载css后才会加上,所以最开始返回的网页内容没有样式,页面依然会闪一下。为了解决这个问题,我们需要让样式也一并在服务端渲染的时候返回。

首先,服务端渲染的时候,解析css文件,不能使用style-loader了,要使用isomorphic-style-loader。

{ test: /\.css$/, use: [ 'isomorphic-style-loader', 'css-loader', 'postcss-loader' ], }

但是,如何在服务端获取到当前路由内的组件样式呢?回想一下,我们在做路由的服务端渲染时,用到了StaticRouter,它会接收一个context对象,这个context对象可以作为一个载体来传递一些信息。我们就用它!

思路就是在渲染组件的时候,在组件内接收context对象,获取组件样式,放到context中,服务端拿到样式,插入到返回的HTML中的style标签中。

来看看组件是如何读取样式的吧:

import style from './style/index.css' class Index extends React.Component { componentWillMount() { if (this.props.staticContext) { const css = styles._getCss() this.props.staticContext.css.push(css) } } }

在路由内的组件可以在props里接收到staticContext,也就是通过StaticRouter传递过来的context,
isomorphic-style-loader 提供了一个 _getCss() 方法,让我们能读取到css样式,然后放到staticContext里。
不在路由之内的组件,可以通过父级组件,传递props的方法,或者用react-router的withRouter包裹一下

其实这部分提取css的逻辑可以写成高阶组件,这样就可以做到复用了

import React, { Component } from 'react' export default (DecoratedComponent, styles) => { return class NewComponent extends Component { componentWillMount() { if (this.props.staticContext) { const css = styles._getCss() this.props.staticContext.css.push(css) } } render() { return <DecoratedComponent {...this.props}/> } } }

在服务端,经过组件的渲染之后,context中已经有内容了,我们这时候把样式处理一下,返回给浏览器,就可以做到样式的服务端渲染了

const serverRender = (req, store) => { const context = {css: []} const template = fs.readFileSync(process.cwd() + '/public/static/index.html', 'utf8') const content = renderToString( <Provider store={store}> <StaticRouter location={req.path} context={context}> <Container/> </StaticRouter> </Provider> ) // 经过渲染之后,context.css内已经有了样式 const cssStr = context.css.length ? context.css.join('\n') : '' const initialState = `<script> window.context = { INITIAL_STATE: ${JSON.stringify(store.getState())} } </script>` return template.replace('<!--app-->', content) .replace('server-render-css', cssStr) .replace('<!--initial-state-->', initialState) }

至此,服务端渲染就全部完成了。

总结

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/f3e6aa452a84db9f182252021d16a6e8.html