详解基于React.js和Node.js的SSR实现方案(2)

export const getData = () => { return (dispatch, getState, axiosInstance) => { return axiosInstance.get('interfaceUrl/xxx') .then((res) => { dispatch({ type: 'HOME_LIST', list: res.list }) }); } }

C. reducer:接收旧的state和action,返回新的state,响应actions并发送到store。

export default (state = { list: [] }, action) => { switch(action.type) { case 'HOME_LIST': return { ...state, list: action.list } default: return state; } } export default (state = { list: [] }, action) => { switch(action.type) { case 'HOME_LIST': return { ...state, list: action.list } default: return state; } }

D. 使用react-redux的connect,Provider把组件和store连接起来

Provider 将之前创建的store作为prop传给Provider

const content = renderToString(( <Provider store={store}> <StaticRouter location={req.path} context={context}> <div> {renderRoutes(routes)} </div> </StaticRouter> </Provider> ));

connect([mapStateToProps],[mapDispatchToProps],[mergeProps], [options])接收四个参数 常用的是前两个属性 mapStateToProps函数允许我们将store中的数据作为props绑定到组件上mapDispatchToProps将action作为props绑定到组件上

connect(mapStateToProps(),mapDispatchToProps())(MyComponent)

(6) 使用react-router承担路由职责 服务端路由不同于客户端,它是无状态的。React 提供了一个无状态的组件StaticRouter,向StaticRouter传递当前URL,调用ReactDOMServer.renderToString() 就能匹配到路由视图。

服务端

import { StaticRouter } from 'react-router-dom'; import { renderRoutes } from 'react-router-config' import routes from './router.js' <StaticRouter location={req.path} context={{context}}> {renderRoutes(routes)} </StaticRouter>

浏览器端

import { BrowserRouter } from 'react-router-dom'; import { renderRoutes } from 'react-router-config' import routes from './router.js' <BrowserRouter> {renderRoutes(routes)} </BrowserRouter>

当浏览器的地址栏发生变化的时候,前端会去匹配路由视图,同时由于req.path发生变化,服务端匹配到路由视图,这样保持了前后端路由视图的一致,在页面刷新时,仍然可以正常显示当前视图。如果只有浏览器端路由,而且是采用BrowserRouter,当页面地址发生变化后去刷新页面时,由于没有对应的HTML,会导致页面找不到,但是加了服务端路由后,刷新发生时服务端会返回一个完整的html给客户端,页面仍然正常显示。 推荐使用 react-router-config插件,然后如上代码在StaticRouter和BrowserRouter标签的子元素里加renderRoutes(routes):建一个router.js文件

const routes = [{ component: Root, routes: [ { path: 'https://www.jb51.net/', exact: true, component: Home, loadData: Home.loadData }, { path: '/child/:id', component: Child, loadData: Child.loadData routes: [ path: '/child/:id/grand-child', component: GrandChild, loadData: GrandChild.loadData ] } ] }];

在浏览器端请求一个地址的时候,server.js 里在实际渲染前可以通过matchRouters 这种方式确定要渲染的内容,调用loaderData函数进行action派发,返回promise->promiseAll->renderToString,最终生成HTML文档返回。

import { matchRoutes } from 'react-router-config' const loadBranchData = (location) => { const branch = matchRoutes(routes, location.pathname) const promises = branch.map(({ route, match }) => { return route.loadData ? route.loadData(match) : Promise.resolve(null) }) return Promise.all(promises) }

(7) 写组件注意代码同构(即:一套React代码在服务端执行一次,在客户端再执行一次) 由于服务器端绑定事件是无效的,所以服务器返回的只有页面样式(&注水的数据),同时返回JavaScript文件,在浏览器上下载并执行JavaScript时才能把事件绑上,而我们希望这个过程只需编写一次代码,这个时候就会用到同构,服务端渲染出样式,在客户端执行时绑上事件。

优点: 共用前端代码,节省开发时间 弊端: 由于服务器端和浏览器环境差异,会带来一些问题,如document等对象找不到,DOM计算报错,前端渲染和服务端渲染内容不一致等;前端可以做非常复杂的请求合并和延迟处理,但为了同构,所有这些请求都在预先拿到结果才会渲染。

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

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