同构流程是这样:
- 当浏览器请求到服务端后,服务端先得到页面数据,用数据渲染react组件,然后转成html string,插入到待返回的html中,然后将html返回给浏览器;
- 在返回给浏览器的html中存在一段“同构代码”,就是react组件用webpack打包之后的js文件。
- 当浏览器获得server返回的html之后,解析html呈现页面,可以快速的显示出html内容(执行到同构代码的时候,会再次渲染一次,但是用户感知不到,react有机制可以将开销最小化)
- 当页面出现交互行为,发请求其实走的就是浏览器端执行的代码了,可以去请求API或者其他操作。
server
index.js
const app = new (require('koa'));const mount = require('koa-mount');const serveStatic = require('koa-static');const getData = require('./get-data')const ReactDOMServer = require('react-dom/server');const App = require('./app.jsx')const template = require('./template')(__dirname + '/index.htm')app.use(mount('/static', serveStatic(__dirname + '/source')))app.use(mount('/data', async (ctx) => {ctx.body = await getData(+(ctx.query.sort || 0), +(ctx.query.filt || 0));}));app.use(async (ctx) => {ctx.status = 200;// 准备数据const filtType = +(ctx.query.filt || 0)const sortType = +(ctx.query.sort || 0);const reactData = await getData(sortType, filtType);// console.log(ReactDOMServer.renderToString(ReactRoot));ctx.body = template({// react组件转化成字符串reactString: ReactDOMServer.renderToString(App(reactData)),reactData,filtType,sortType})})app.listen(3000)// module.exports = app;
app.jsx
const React = require('react')const Container = require('../component/container.jsx')module.exports = function (reactData) {return <Containercolumns={reactData}filt={() => { }}sort={() => { }}/>}
“../component/container.jsx”就是一个react 组件,不贴了;
static也不贴了;
template就是之前讲的模版引擎代码,也不贴了;
get-data.js
const listClient = require('./list-client');module.exports = async function (sortType = 0, filtType = 0) {// 使用微服务拉取数据const data = await new Promise((resolve, reject) => {// 这里RPC请求去拿数据listClient.write({sortType,filtType}, function (err, res) {err ? reject(err) : resolve(res.columns);})});return data}
listClient.js是利用easy-sock去后端发起全双工RPC通信拿数据,也不贴了。
browser
浏览器部分主要就是一个react代码,以及webpack的打包配置,生成的文件放到static里面
index.jsx
const Container = require('../component/container.jsx');const React = require('react');const ReactDOM = require('react-dom');class App extends React.Component {constructor() {super();this.state = {columns: reactInitData,filtType: reactInitFiltType,sortType: reactInitSortType}}render() {return (<Containercolumns={this.state.columns}filt={(filtType) => {fetch(`./data?sort=${this.state.sortType}&filt=${filtType}`).then(res => res.json()).then(json => {this.setState({columns: json,filtType: filtType})})}}sort={(sortType) => {fetch(`./data?sort=${sortType}&filt=${this.state.filtType}`).then(res => res.json()).then(json => {this.setState({columns: json,sortType: sortType})})}}/>)}}ReactDOM.render(<App />,document.getElementById('reactapp'))
这里和serverRender中引用的都是同样的组件,挂的都是#reactapp的节点,但是不同的是,这里需要处理交互事件,所以可以看到刚才在server端的sort、filter都是空函数,而在这里都是fetch并setState了。
最后,看下webpack.congif.js
module.exports = {mode: 'development',devtool: false,entry: __dirname + '/index.jsx',output: {filename: 'main.js',path: __dirname + '/../node/source/'},module: {rules: [{test: /\.jsx$/, use: {loader: 'babel-loader',options: {presets: ['@babel/preset-react']}}}]}}
