同构流程是这样:

  • 当浏览器请求到服务端后,服务端先得到页面数据,用数据渲染react组件,然后转成html string,插入到待返回的html中,然后将html返回给浏览器;
  • 在返回给浏览器的html中存在一段“同构代码”,就是react组件用webpack打包之后的js文件。
  • 当浏览器获得server返回的html之后,解析html呈现页面,可以快速的显示出html内容(执行到同构代码的时候,会再次渲染一次,但是用户感知不到,react有机制可以将开销最小化)
  • 当页面出现交互行为,发请求其实走的就是浏览器端执行的代码了,可以去请求API或者其他操作。

server

index.js

  1. const app = new (require('koa'));
  2. const mount = require('koa-mount');
  3. const serveStatic = require('koa-static');
  4. const getData = require('./get-data')
  5. const ReactDOMServer = require('react-dom/server');
  6. const App = require('./app.jsx')
  7. const template = require('./template')(__dirname + '/index.htm')
  8. app.use(mount('/static', serveStatic(__dirname + '/source')))
  9. app.use(mount('/data', async (ctx) => {
  10. ctx.body = await getData(+(ctx.query.sort || 0), +(ctx.query.filt || 0));
  11. }));
  12. app.use(async (ctx) => {
  13. ctx.status = 200;
  14. // 准备数据
  15. const filtType = +(ctx.query.filt || 0)
  16. const sortType = +(ctx.query.sort || 0);
  17. const reactData = await getData(sortType, filtType);
  18. // console.log(ReactDOMServer.renderToString(ReactRoot));
  19. ctx.body = template({
  20. // react组件转化成字符串
  21. reactString: ReactDOMServer.renderToString(
  22. App(reactData)
  23. ),
  24. reactData,
  25. filtType,
  26. sortType
  27. })
  28. })
  29. app.listen(3000)
  30. // module.exports = app;

app.jsx

  1. const React = require('react')
  2. const Container = require('../component/container.jsx')
  3. module.exports = function (reactData) {
  4. return <Container
  5. columns={reactData}
  6. filt={() => { }}
  7. sort={() => { }}
  8. />
  9. }

“../component/container.jsx”就是一个react 组件,不贴了;
static也不贴了;
template就是之前讲的模版引擎代码,也不贴了;

get-data.js

  1. const listClient = require('./list-client');
  2. module.exports = async function (sortType = 0, filtType = 0) {
  3. // 使用微服务拉取数据
  4. const data = await new Promise((resolve, reject) => {
  5. // 这里RPC请求去拿数据
  6. listClient.write({
  7. sortType,
  8. filtType
  9. }, function (err, res) {
  10. err ? reject(err) : resolve(res.columns);
  11. })
  12. });
  13. return data
  14. }

listClient.js是利用easy-sock去后端发起全双工RPC通信拿数据,也不贴了。

browser

浏览器部分主要就是一个react代码,以及webpack的打包配置,生成的文件放到static里面
index.jsx

  1. const Container = require('../component/container.jsx');
  2. const React = require('react');
  3. const ReactDOM = require('react-dom');
  4. class App extends React.Component {
  5. constructor() {
  6. super();
  7. this.state = {
  8. columns: reactInitData,
  9. filtType: reactInitFiltType,
  10. sortType: reactInitSortType
  11. }
  12. }
  13. render() {
  14. return (
  15. <Container
  16. columns={this.state.columns}
  17. filt={(filtType) => {
  18. fetch(`./data?sort=${this.state.sortType}&filt=${filtType}`)
  19. .then(res => res.json())
  20. .then(json => {
  21. this.setState({
  22. columns: json,
  23. filtType: filtType
  24. })
  25. })
  26. }}
  27. sort={(sortType) => {
  28. fetch(`./data?sort=${sortType}&filt=${this.state.filtType}`)
  29. .then(res => res.json())
  30. .then(json => {
  31. this.setState({
  32. columns: json,
  33. sortType: sortType
  34. })
  35. })
  36. }}
  37. />
  38. )
  39. }
  40. }
  41. ReactDOM.render(
  42. <App />,
  43. document.getElementById('reactapp')
  44. )

这里和serverRender中引用的都是同样的组件,挂的都是#reactapp的节点,但是不同的是,这里需要处理交互事件,所以可以看到刚才在server端的sort、filter都是空函数,而在这里都是fetch并setState了。
最后,看下webpack.congif.js

  1. module.exports = {
  2. mode: 'development',
  3. devtool: false,
  4. entry: __dirname + '/index.jsx',
  5. output: {
  6. filename: 'main.js',
  7. path: __dirname + '/../node/source/'
  8. },
  9. module: {
  10. rules: [
  11. {
  12. test: /\.jsx$/, use: {
  13. loader: 'babel-loader',
  14. options: {
  15. presets: ['@babel/preset-react']
  16. }
  17. }
  18. }
  19. ]
  20. }
  21. }