# react-router-renderProps renderProps of match method of react router 最近在学习服务端渲染,之前一直不明白match方法中的renderProps,所以现在打印了log,查看了内部的结构 ### 1.match方法 ```js match({ history, routes: getRoutes(store), location: req.originalUrl }, (error, redirectLocation, renderProps) => {}) ``` 知道服务器端渲染的都知道这个match方法,我这里不再班门弄斧 ### 2.renderProps属性 首先我给出我自己配置的React-router: ```js { path:"/", component : App, IndexRoute:{ component : Home }, childRoutes:[ { component : Home, path:"home" } ] } ``` 下面是完整的renderProps属性,其中renderProps包含routes,params,location,components,router,matchContext属性。 ```js { //renderProps包含routes对象 routes: [ { path: '/', component: [Function: App], IndexRoute: [Object], childRoutes: [Object] }, { component: [Function: Home], path: 'home' } ], //renderProps包含params对象 params: {}, // renderProps包含location对象 location: { pathname: '/home', search: '', hash: '', state: undefined, action: 'POP', key: '34hg49', query: {} }, // renderProps包含components对象 components: [ [Function: App], [Function: Home] ], // renderProps包含router对象 router: { getCurrentLocation: [Function: getCurrentLocation], listenBefore: [Function: listenBefore], listen: [Function: listen], transitionTo: [Function: transitionTo], push: [Function: push], replace: [Function: replace], go: [Function: go], goBack: [Function: goBack], goForward: [Function: goForward], createKey: [Function: createKey], createPath: [Function: createPath], createHref: [Function: createHref], createLocation: [Function: createLocation], canGo: [Function: canGo], unsubscribe: [Function: unsubscribe], setRouteLeaveHook: [Function: listenBeforeLeavingRoute], isActive: [Function: isActive], location: { pathname: '/home', search: '', hash: '', state: undefined, action: 'POP', key: '34hg49', query: {} }, params: {}, routes: [ [Object], [Object] ] }, // renderProps包含matchContext对象 matchContext: { transitionManager: { isActive: [Function: isActive], match: [Function: match], listenBeforeLeavingRoute: [Function: listenBeforeLeavingRoute], listen: [Function: listen] }, router: { getCurrentLocation: [Function: getCurrentLocation], listenBefore: [Function: listenBefore], listen: [Function: listen], transitionTo: [Function: transitionTo], push: [Function: push], replace: [Function: replace], go: [Function: go], goBack: [Function: goBack], goForward: [Function: goForward], createKey: [Function: createKey], createPath: [Function: createPath], createHref: [Function: createHref], createLocation: [Function: createLocation], canGo: [Function: canGo], unsubscribe: [Function: unsubscribe], setRouteLeaveHook: [Function: listenBeforeLeavingRoute], isActive: [Function: isActive], location: [Object], params: {}, routes: [Object] } } } ``` 我相信,当你了解了renderProps的内部结构以后,你能够更好的理解网上的关于react-router服务器渲染的例子了。如果觉得有用别忘了star哦~~~~ ### 3.renderToString返回的一个内容 ```html Widgets
ID 颜色 Sprockets 所有者
1 Red 7 John
2 Taupe 1 George
3 Green 8 Ringo
4 Blue 2 Paul
``` 你可以查看[react-universal-bucket这个例子](https://github.com/liangklfangl/react-universal-bucket)。上面是renderToString输出的内容,你要学会关注*data-react-checksum*和*data-reactid*和*window.__data* ### 4.renderToString的真实作用 #### 4.1 renderToString本身是静态的 之所以说renderToString是静态的,是因为其如果仅仅是调用它来渲染组件树那么其只是一个模版语言而已。React提供了一个API用于将虚拟DOM树在服务端环境下进行渲染,这个API是ReactDom/server中的renderToString。这个方法接受一个虚拟DOM树,并*返回一个渲染后的HTML字符串*,例如我有一个根级组件Root,我便可以使用下列语句得到结果: ```js import {renderToString} from 'ReactDom/server'; const markup = renderToString( ); ``` markup即为渲染后的结果。renderToString这个方法是服务端渲染的基础,但如果只是单纯这样使用,那么基本等于将React作为一个复杂很多的*模板语言*来写而已,因为*这个渲染并不会理会任何的ajax请求(没有请求也就是说在渲染我们组件之前无法自定义组件需要的数据,这是硬伤!!可以使用下文的redux的Provider将store中的数据传递给我们的组件来解决),同时也不会根据url来做任何路由,它只会在第一次render方法调用后结束*。这也就是说render方法之后的所有生命周期函数都不会被触发,在一次服务端渲染中,*只有constructor、componentWillMount和render会被各触发一次,并且在期间使用setState也是没有意义的*。 这显然不是我们期望的,为了愉快得满足我们的须有,这里有两个问题需要解决:
(1)路由(使用react-router来监听location变化从而重新渲染组件,此处不做讲解)。
(2)ajax请求(而不是将React作为一个模板语言,先发送ajax请求获取到store状态再渲染组件树,redux-async-connect完成或者中间件自己处理)。
#### 4.2 renderToString结合redux处理ajax请求 (1)通过redux-async-connect来处理ajax请求并自动渲染 由于服务端渲染只会走一遍生命周期,并且在第一次render后便会停止,所以想要真正渲染出最终的页面,我们必须在第一次渲染前就将状态准备好。这也就是说,我们必须要有一次超前的ajax请求实现获取状态,然后来根据这个状态渲染我们最终的组件树: ```js function hydrateOnClient() { res.send('\n' + renderToString()); } //这里的match方法是在express的中间件中处理 match({ history, routes: getRoutes(store), location: req.originalUrl }, (error, redirectLocation, renderProps) => { if (redirectLocation) { res.redirect(redirectLocation.pathname + redirectLocation.search); //重定向要添加pathname+search } else if (error) { console.error('ROUTER ERROR:', pretty.render(error)); res.status(500); hydrateOnClient(); } else if (renderProps) { //loadOnServer和ReduxAsyncConnect来自于redux-async-connect库 loadOnServer({...renderProps, store, helpers: {client}}).then(() => { const component = ( //这里不是 //https://zhuanlan.zhihu.com/p/22875338 <\/Provider> ); res.status(200); global.navigator = {userAgent: req.headers['user-agent']}; res.send('\n' + renderToString()); //component表示要渲染为html字符串的组件树,store来自于redux }); } else { res.status(404).send('Not found'); } }); } ``` 在Provider中我们提供了一个store属性,我们看看Html组件的处理逻辑你就明白了: ```js import React, {Component, PropTypes} from 'react'; import {renderToString} from 'react-dom/server'; import serialize from 'serialize-javascript'; // https://github.com/liangklfang/serialize-javascript import Helmet from 'react-helmet'; export default class Html extends Component { render() { const {assets, component, store} = this.props; const content = component ? renderToString(component) : ''; //我们的component属性表示最外层组件为Provider,其会接收redux的store作为参数 const head = Helmet.rewind(); return ( {Object.keys(assets.styles).map((style, key) => )}
{{markup}}
``` window.initState便拥有了我们服务端渲染后的状态,如此,客户端便有了一个途径来根据这个状态来*初始化客户端的store*,并接续接下来的操作,这实质上是完成了服务端和客户端之间状态的对接。 参考资料: [【React/Redux】深入理解React服务端渲染](https://zhuanlan.zhihu.com/p/22875338) [【React/Redux/Router/Immutable】React最佳实践的正确食用姿势](https://zhuanlan.zhihu.com/p/22874997) [server-rendering](https://github.com/liangklfang/BlogReworkPro)