基于React+Redux的SSR实现方法(3)

1、我们明确知道请求的页面需要什么样的数据。我们获取数据并使用该数据创建 Redux 存储。然后我们通过提供已完成的 Store 来呈现页面,理论上我们可以做到。

2、我们完全依赖于运行在客户端上的代码,计算出最终的结果。

第一种方法,需要我们在两端做好状态管理。第二种方法需要我们在服务端使用一些额外的库或工具,来确保同一套代码能在服务端和客户端做相同的事情,我个人比较推荐使用这种方法。

例如,我们使用了 Fetch API 向后端发出异步请求,而服务端默认是不支持的。我们需要做的就是在 server.js 中将 Fetch 导入:

import 'isomorphic-fetch';

我们使用客户端API接收异步数据,一旦 Store 获取到异步数据,我们将触发 ReactDOMServer.renderToString 。它会提供给我们想要的标记。我们的Express处理器是这样的:

app.get('*', (req, res) => { const store = createStore(); const unsubscribe = store.subscribe(() => { const users = getUsers(store.getState()); if (users !== null && users.length > 0) { unsubscribe(); const content = ReactDOMServer.renderToString( <Provider store={ store }><App /></Provider> ); res.set('Content-Type', 'text/html'); res.send(` <html> <head> <title>App</title> </head> <body> <div>${ content }</div> <script src="https://www.jb51.net/bundle.js"></script> </body> </html> `); } }); ReactDOMServer.renderToString(<Provider store={ store }><App /></Provider>); });

我们使用 Store 的 subscribe 方法来监听状态。当状态发生变化——是否有任何用户数据被获取。如果 users 存在,我们将 unsubscribe() ,这样我们就不会让相同的代码运行两次,并且我们使用相同的存储实例转换为string。最后,我们将标记输出到浏览器。

store.subscribe方法返回一个函数,调用这个函数就可以解除监听

有了上面的代码,我们的组件已经可以成功地在服务器端渲染。通过开发者工具,我们可以看到发送到浏览器的内容:

<html> <head> <title>App</title> <style> body { font-size: 18px; font-family: Verdana; } </style> </head> <body> <div><div data-reactroot=""><p>Eve Holt</p><p>Charles Morris</p><p>Tracey Ramos</p></div></div> <script> window.__APP_STATE = {"users":[{"id":4,"first_name":"Eve","last_name":"Holt","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/marcoramires/128.jpg"},{"id":5,"first_name":"Charles","last_name":"Morris","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/stephenmoon/128.jpg"},{"id":6,"first_name":"Tracey","last_name":"Ramos","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/bigmancho/128.jpg"}]}; </script> <script src="https://www.jb51.net/bundle.js"></script> </body> </html>

当然,现在并没有结束,客户端 JavaScript 不知道服务器上发生了什么,也不知道我们已经对API进行了请求。我们必须通过传递 Store 的状态来通知浏览器,以便它能够接收它。

const content = ReactDOMServer.renderToString( <Provider store={ store }><App /></Provider> ); res.set('Content-Type', 'text/html'); res.send(` <html> <head> <title>App</title> </head> <body> <div>${ content }</div> <script> window.__APP_STATE = ${ JSON.stringify(store.getState()) }; </script> <script src="https://www.jb51.net/bundle.js"></script> </body> </html> `);

我们将 Store 状态放到一个全局变量 __APP_STATE 中, reducer 也有一点变化:

function getInitialState() { if (typeof window !== 'undefined' && window.__APP_STATE) { return window.__APP_STATE; } return { users: null }; }

注意 typeof window !== 'undefined' ,我们必须这样做,因为这段代码也会在服务端执行,这就是为什么说在做服务端渲染时要非常小心,尤其是全局使用的浏览器api的时候。

最后一个需要优化的地方,就是当已经取到 users 时,必须阻止 fetch 。

componentWillMount() { const { users, fetchUsers } = this.props; if (users === null) { fetchUsers(); } }

总结

服务器端呈现是一个有趣的话题。它有很多优势,并改善了整体用户体验。它还会提升你的单页应用程序的SEO。但这一切并不简单。在大多数情况下,需要额外的工具和精心选择的api。

这只是一个简单的案例,实际开发场景往往比这个复杂的多,需要考虑的情况也会非常多,你们的服务端渲染是怎么做的?

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

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