Äú¿ÉÒÔ¾èÖú£¬Ö§³ÖÎÒÃǵĹ«ÒæÊÂÒµ¡£

1Ôª 10Ôª 50Ôª





ÈÏÖ¤Â룺  ÑéÖ¤Âë,¿´²»Çå³þ?Çëµã»÷Ë¢ÐÂÑéÖ¤Âë ±ØÌî



  ÇóÖª ÎÄÕ ÎÄ¿â Lib ÊÓÆµ iPerson ¿Î³Ì ÈÏÖ¤ ×Éѯ ¹¤¾ß ½²×ù Model Center   Code  
»áÔ±   
   
 
     
   
 ¶©ÔÄ
  ¾èÖú
React + Koa ʵÏÖ·þÎñ¶ËäÖȾ(SSR)
 
  2280  次浏览      27
  2019-1-31
 
±à¼­ÍƼö:
±¾ÎÄÀ´×ÔÍøÂ磬±¾ÎÄÖ÷Òª½éÉÜÁËÔÚÒ»¸öReact AppÖÐÈçºÎʹÓ÷þÎñ¶Ëkoa2À´ÊµÏÖÎÒÃǵķþÎñ¶ËäÖȾ¡£Ï£Íû¶ÔÄúµÄѧϰÓÐËù°ïÖú¡£

ReactÊÇĿǰǰ¶ËÉçÇø×îÁ÷ÐеÄUI¿âÖ®Ò»£¬ËüµÄ»ùÓÚ×é¼þ»¯µÄ¿ª·¢·½Ê½¼«´óµØÌáÉýÁËǰ¶Ë¿ª·¢ÌåÑ飬Reactͨ¹ý²ð·ÖÒ»¸ö´óµÄÓ¦ÓÃÖÁÒ»¸ö¸öСµÄ×é¼þ£¬À´Ê¹µÃÎÒÃǵĴúÂë¸ü¼ÓµÄ¿É±»ÖØÓã¬ÒÔ¼°»ñµÃ¸üºÃµÄ¿Éά»¤ÐÔ£¬µÈµÈ»¹ÓÐÆäËûºÜ¶àµÄÓŵã...

ͨ¹ýReact, ÎÒÃÇͨ³£»á¿ª·¢Ò»¸öµ¥Ò³Ó¦Óã¨SPA£©£¬µ¥Ò³Ó¦ÓÃÔÚä¯ÀÀÆ÷¶Ë»á±È´«Í³µÄÍøÒ³ÓиüºÃµÄÓû§ÌåÑ飬ä¯ÀÀÆ÷Ò»°ã»áÄõ½Ò»¸öbodyΪ¿ÕµÄhtml£¬È»ºó¼ÓÔØscriptÖ¸¶¨µÄjs, µ±ËùÓÐjs¼ÓÔØÍê±Ïºó£¬¿ªÊ¼Ö´ÐÐjs, ×îºóÔÙäÖȾµ½domÖÐ, ÔÚÕâ¸ö¹ý³ÌÖУ¬Ò»°ãÓû§Ö»Äܵȴý£¬Ê²Ã´¶¼×ö²»ÁË£¬Èç¹ûÓû§ÔÚÒ»¸ö¸ßËÙµÄÍøÂçÖУ¬¸ßÅäÖõÄÉ豸ÖУ¬ÒÔÉÏÏÈÒª¼ÓÔØËùÓеÄjsÈ»ºóÔÙÖ´ÐеĹý³Ì¿ÉÄܲ»ÊÇʲô´óÎÊÌ⣬µ«ÊÇÓкܶàÇé¿öÊÇÎÒÃǵÄÍøËÙÒ»°ã£¬É豸Ҳ¿ÉÄܲ»ÊÇ×îºÃµÄ£¬ÔÚÕâÖÖÇé¿öϵĵ¥Ò³Ó¦ÓÿÉÄܶÔÓû§À´ËµÊǸöºÜ²îµÄÓû§ÌåÑ飬Óû§¿ÉÄÜ»¹Ã»ÌåÑéµ½ä¯ÀÀÆ÷¶ËSPAµÄºÃ´¦Ê±£¬¾ÍÒѾ­Àë¿ªÍøÕ¾ÁË£¬ÕâÑùµÄ»°ÄãµÄÍøÕ¾×öµÄÔÙºÃÒ²²»»áÓÐÌ«¶àµÄä¯ÀÀÁ¿¡£

µ«ÊÇÎÒÃÇ×ܲ»Äܻص½ÒÔǰµÄÒ»¸öÒ³ÃæÒ»¸öÒ³ÃæµÄ´«Í³¿ª·¢°É£¬ÏÖ´ú»¯µÄUI¿â¶¼ÌṩÁË·þÎñ¶ËäÖȾ(SSR)µÄ¹¦ÄÜ£¬Ê¹µÃÎÒÃÇ¿ª·¢µÄSPAÓ¦ÓÃÒ²ÄÜÍêÃÀµÄÔËÐÐÔÚ·þÎñ¶Ë£¬´ó´ó¼Ó¿ìÁËÊׯÁäÖȾµÄʱ¼ä£¬ÕâÑùµÄ»°Óû§¼ÈÄܸü¿ìµÄ¿´µ½ÍøÒ³µÄÄÚÈÝ£¬Óë´Ëͬʱ£¬ä¯ÀÀÆ÷ͬʱ¼ÓÔØÐèÒªµÄjs£¬¼ÓÔØÍêºó°ÑËùÓеÄdomʼþ£¬¼°¸÷ÖÖ½»»¥Ìí¼Óµ½Ò³ÃæÖУ¬×îºó»¹ÊÇÒÔÒ»¸öSPAµÄÐÎʽÔËÐУ¬ÕâÑùµÄ»°ÎÒÃǼÈÌáÉýÁËÊׯÁäÖȾµÄʱ¼ä£¬ÓÖÄÜ»ñµÃSPAµÄ¿Í»§¶ËÓû§ÌåÑ飬¶ÔÓÚSEOÒ²ÊǸö±ØÐëµÄ¹¦ÄÜ??¡£

OK£¬ÎÒÃÇ´óÖÂÁ˽âÁËSSRµÄ±ØÒªÐÔ£¬ÏÂÃæÎÒÃǾͿÉÒÔÔÚÒ»¸öReact AppÖÐÀ´ÊµÏÖ·þÎñ¶ËäÖȾµÄ¹¦ÄÜ£¬BTW, ¼ÈÈ»ÎÒÃÇÒѾ­´¦ÔÚÒ»¸öµ½´¦ÊÇasync/awaitµÄ»·¾³ÖУ¬ÕâÀïµÄ·þÎñ¶ËÎÒÃÇʹÓÃkoa2À´ÊµÏÖÎÒÃǵķþÎñ¶ËäÖȾ¡£

³õʼ»¯Ò»¸öÆÕͨµÄµ¥Ò³Ó¦ÓÃSPA

Ê×ÏÈÎÒÃÇÏȲ»¹Ü·þÎñ¶ËäÖȾµÄ¶«Î÷£¬ÎÒÃÇÏÈ´´½¨Ò»¸ö»ùÓÚReactºÍReact-RouterµÄSPA£¬µÈÎÒÃǰÑÒ»¸öÍêÕûµÄSPA´´½¨ºÃºó£¬ÔÙ¼ÓÈëSSRµÄ¹¦ÄÜÀ´×î´ó»¯ÌáÉýappµÄÐÔÄÜ¡£

Ê×ÏȽøÈëappÈë¿Ú App.js:

import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route } from 'react-router-dom';
const Home = () => <div>Home</div>;
const Hello = () => <div>Hello</div>;
const App = () => {
return (
<Router>
<Route exact path="/" component={Home} />
<Route exact path="/hello" component={Hello} />
</Router>
)
}
ReactDOM.render(<App/>, document.getElementById('app'))

¸´ÖÆ´úÂëÉÏÃæÎÒÃÇΪ·ÓÉ/ ºÍ /hello´´½¨ÁË2¸öÖ»ÊÇäÖȾһЩÎÄ×Öµ½Ò³ÃæµÄ×é¼þ¡£µ«µ±ÎÒÃǵÄÏîÄ¿±äµÃÔ½À´Ô½´ó£¬×é¼þÔ½À´Ô½¶à£¬×îÖÕÎÒÃÇ´ò°ü³öÀ´µÄjs¿ÉÄÜ»á±äµÃºÜ´ó£¬ÉõÖÁ±äµÃ²»¿É¿Ø£¬ËùÒÔÄØÎÒÃǵÚÒ»²½ÐèÒªÓÅ»¯µÄÊÇ´úÂë²ð·Ö£¨code-splitting£©£¬ÐÒÔ˵ÄÊÇͨ¹ýwebpack dynamic import ºÍ react-loadable£¬ÎÒÃÇ¿ÉÒÔºÜÈÝÒ××öµ½ÕâÒ»µã¡£

ÓÃReact-LoadableÀ´Ê±¼ä´úÂë²ð·Ö

ʹÓÃ֮ǰ£¬ÏȰ²×° react-loadable:

npm install react-loadable
# or
yarn add react-loadable

È»ºóÔÚÄãµÄ javascriptÖÐ:

//...
import Loadable from 'react-loadable';
//...
const AsyncHello = Loadable({
loading: <div>loading...</div>,
//°ÑÄãµÄHello×é¼þдµ½µ¥¶ÀµÄÎļþÖÐ
//È»ºóʹÓÃwebpackµÄ dynamic import
loader: () => import('./Hello'),
})
//È»ºóÔÚÄãµÄ·ÓÉÖÐʹÓÃloadable°ü×°¹ýµÄ×é¼þ:
<Route exact path="/hello" component={AsyncHello} />

ºÜ¼òµ¥°É£¬ÎÒÃÇÖ»ÐèÒªimport react-loadable£¬ È»ºó´«Ò»Ð©option½øÈ¥¾ÍÐÐÁË£¬ÆäÖеÄloadingÑ¡ÏîÊǵ±¶¯Ì¬¼ÓÔØHello×é¼þËùÐèµÄjsʱ£¬äÖȾloading×é¼þ£¬¸øÓû§Ò»ÖÖ¼ÓÔØÖеĸоõ£¬ÌåÑéÒ²»á±Èʲô¶¼²»¼ÓºÃ¡£

ºÃÁË£¬ÏÖÔÚÈç¹ûÎÒÃÇ·ÃÎÊÊ×Ò³µÄ»°£¬Ö»ÓÐHome×é¼þÒÀÀµµÄjs²Å»á±»¼ÓÔØ£¬È»ºóµã»÷ij¸öÁ´½Ó½øÈëhelloÒ³ÃæµÄ»°£¬»áÏÈäÖȾloading×é¼þ£¬²¢Í¬Ê±Òì²½¼ÓÔØhello×é¼þÒÀÀµµÄjs£¬¼ÓÔØÍêºó£¬Ìæ»»µôloadingÀ´äÖȾhello×é¼þ¡£Í¨¹ý»ùÓÚ·Óɲð·Ö´úÂëµ½²»Í¬µÄ´úÂë¿é£¬ÎÒÃǵÄSPAÒѾ­ÓÐÁ˺ܴóµÄÓÅ»¯£¬cheers??¡£¸üµðµÄÊÇreact-loadableͬÑùÖ§³ÖSSR£¬ËùÒÔÄã¿ÉÒÔÔÚÈÎÒâµØ·½Ê¹ÓÃreact-loadable£¬²»¹ÜÊÇÔËÐÐÔÚǰ¶Ë»¹ÊÇ·þÎñ¶Ë£¬ÒªÈÃreact-loadableÔÚ·þÎñ¶ËÕý³£ÔËÐеϰÎÒÃÇÐèÒª×öһЩ¶îÍâµÄÅäÖ㬱¾ÎĺóÃæ»á½²µ½£¬ÏȲ»¼±??¡£?

µ½ÕâÀïÎÒÃÇÒѾ­´´½¨ºÃÒ»¸ö»ù±¾µÄReact SPA£¬¼ÓÉÏ´úÂë²ð·Ö£¬ÎÒÃǵÄappÒѾ­ÓÐÁ˲»´íµÄÐÔÄÜ£¬µ«ÊÇÎÒÃÇ»¹¿ÉÒÔ¸ü¼Ó¼«ÖµÄÓÅ»¯appµÄÐÔÄÜ£¬ÏÂÃæÎÒÃÇͨ¹ýÔö¼ÓSSRµÄ¹¦ÄÜÀ´½øÒ»²½ÌáÉý¼ÓÔØËÙ¶È£¬Ë³±ã½â¾öÒ»ÏÂSPAÖеÄSEOÎÊÌâ??¡£

¼ÓÈë·þÎñ¶ËäÖȾ£¨SSR£©¹¦ÄÜ

Ê×ÏÈÎÒÃÇÏȴһ¸ö×î¼òµ¥µÄkoa web·þÎñÆ÷£º

npm install koa koa-router

È»ºóÔÚkoaµÄÈë¿ÚÎļþapp.jsÖÐ:

const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
router.get('*', async (ctx) => {
ctx.body = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React SSR</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="/bundle.js"></script>
</body>
</html>
`;
});
app.use(router.routes());
app.listen(3000, '0.0.0.0');

ÉÏÃæ*·ÓÉ´ú±íÈÎÒâµÄurl½øÀ´ÎÒÃǶ¼Ä¬ÈÏäÖȾÕâ¸öhtml£¬°üÀ¨htmlÖдò°ü³öÀ´µÄjs£¬ÄãÒ²¿ÉÒÔÓÃһЩ·þÎñ¶ËÄ£°åÒýÇæ(È磺nunjucks)À´Ö±½ÓäÖȾhtmlÎļþ£¬ÔÚwebpack´ò°üʱͨ¹ýhtml-webpack-pluginÀ´×Ô¶¯²åÈë´ò°ü³öÀ´µÄjs/css×ÊԴ·¾¶¡£

OK, ÎÒÃǵļòÒ×koa serverºÃÁË£¬½ÓÏÂÀ´ÎÒÃÇ¿ªÊ¼±àдReact SSRµÄÈë¿ÚÎļþAppSSR.js£¬ÕâÀïÎÒÃÇÐèҪʹÓÃStaticRouterÀ´´úÌæÖ®Ç°µÄBrowserRouter£¬ÒòΪÔÚ·þÎñ¶Ë£¬Â·ÓÉÊǾ²Ì¬µÄ£¬ÓÃBrowserRouterµÄ»°ÊDz»Æð×÷Óõģ¬ºóÃæ»¹»á×öһЩÅäÖÃÀ´Ê¹µÃreact-loadableÔËÐÐÔÚ·þÎñ¶Ë¡£

Ìáʾ: Äã¿ÉÒÔ°ÑÕû¸önode¶ËµÄ´úÂëÓÃES6/JSX·ç¸ñ±àд£¬¶ø²»ÊDz¿·Öcommonjs£¬²¿·ÖJSX, µ«ÕâÑùµÄ»°ÄãÐèÒªÓÃwebpack°ÑÕû¸ö·þÎñ¶ËµÄ´úÂë±àÒë³Écommonjs·ç¸ñ£¬²ÅÄÜʹµÃËüÔËÐÐÔÚnode»·¾³ÖУ¬ÕâÀïµÄ»°ÎÒÃǰÑReact SSRµÄ´úÂëµ¥¶À³é³öÈ¥£¬È»ºóÔÚÆÕͨµÄnode´úÂëÀïÈ¥requireËü¡£ÒòΪ¿ÉÄÜÔÚÒ»¸öÏÖÓеÄÏîÄ¿ÖУ¬Ö®Ç°¶¼ÊÇcommonjsµÄ·ç¸ñ£¬°ÑÒÔǰµÄnode´úÂëÒ»´ÎÐÔת³ÉES6µÄ»°³É±¾Óеã´ó£¬µ«ÊÇ¿ÉÒÔºóÆÚÒ»²½²½µÄÔÙÇ¨ÒÆ¹ýÈ¥

OK, ÏÖÔÚÔÚÄãµÄ AppSSR.jsÖÐ:

import React from 'react';
//ʹÓþ²Ì¬ static router
import { StaticRouter } from 'react-router-dom';
import ReactDOMServer from 'react-dom/server';
import Loadable from 'react-loadable';
//ÏÂÃæÕâ¸öÊÇÐèÒªÈÃreact-loadableÔÚ·þÎñ¶Ë¿ÉÔËÐÐÐèÒªµÄ£¬ÏÂÃæ»á½²µ½
import { getBundles } from 'react-loadable/webpack';
import stats from '../build/react-loadable.json';
//ÕâÀï°Éreact-routerµÄ·ÓÉÉèÖóé³öÈ¥£¬Ê¹µÃÔÚä¯ÀÀÆ÷¸ú·þÎñ¶Ë¿ÉÒÔ¹²ÓÃ
//ÏÂÃæÒ²»á½²µ½...
import AppRoutes from 'src/AppRoutes';
//ÕâÀïÎÒÃÇ´´½¨Ò»¸ö¼òµ¥µÄclass£¬±©Â¶Ò»Ð©·½·¨³öÈ¥£¬È»ºóÔÚkoa·ÓÉÀïÈ¥µ÷ÓÃÀ´ÊµÏÖ·þÎñ¶ËäÖȾ
class SSR {
//koa ·ÓÉÀï»áµ÷ÓÃÕâ¸ö·½·¨
render(url, data) {
let modules = [];
const context = {};
const html = ReactDOMServer.renderToString(
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<StaticRouter location={url} context={context}>
<AppRoutes initialData={data} />
</StaticRouter>
</Loadable.Capture>
);
//»ñÈ¡·þÎñ¶ËÒѾ­äÖȾºÃµÄ×é¼þÊý×é
let bundles = getBundles(stats, modules);
return {
html,
scripts: this.generateBundleScripts(bundles),
};
}
//°ÑSSR¹ýµÄ×é¼þ¶¼×ª³Éscript±êÇ©ÈÓµ½htmlÀï
generateBundleScripts(bundles) {
return bundles.filter(bundle => bundle.file.endsWith('.js')).map(bundle => {
return `<script type="text/javascript" src="${bundle.file}"></script>\n`;
});
}
static preloadAll() {
return Loadable.preloadAll();
}
}
export default SSR;

µ±±àÒëÕâ¸öÎļþµÄʱºò£¬ÔÚwebpackÅäÖÃÀïʹÓÃtarget: "node" ºÍ externals£¬²¢ÇÒÔÚÄãµÄ´ò°üǰ¶ËappµÄwebpackÅäÖÃÖУ¬ÐèÒª¼ÓÈëreact-loadableµÄ²å¼þ£¬appµÄ´ò°üÐèÒªÔÚssr´ò°ü֮ǰÔËÐУ¬²»È»Äò»µ½react-loadableÐèÒªµÄ¸÷×é¼þÐÅÏ¢£¬ÏÈÀ´¿´appµÄ´ò°ü£º

//webpack.config.dev.js, app bundle
const ReactLoadablePlugin = require('react-loadable/webpack')
.ReactLoadablePlugin;
module.exports = {
//...
plugins: [
//...
new ReactLoadablePlugin({ filename: './build/react-loadable.json', }),
]
}

ÔÚ.babelrcÖмÓÈëloadable plugin:

{
"plugins": [
"syntax-dynamic-import",
"react-loadable/babel",
["import-inspector", {
"serverSideRequirePath": true
}]
]
}

ÉÏÃæµÄÅäÖûáÈÃreact-loadableÖªµÀÄÄЩ×é¼þ×îÖÕÔÚ·þÎñ¶Ë±»äÖȾÁË£¬È»ºóÖ±½Ó²åÈëµ½html script±êÇ©ÖУ¬²¢ÔÚǰ¶Ë³õʼ»¯Ê±°ÑSSR¹ýµÄ×é¼þ¿¼ÂÇÔÚÄÚ£¬±ÜÃâÖØ¸´¼ÓÔØ£¬ÏÂÃæÊÇSSRµÄ´ò°ü£º

//webpack.ssr.js
const nodeExternals = require('webpack-node-externals');
module.exports = {
//...
target: 'node',
output: {
path: 'build/node',
filename: 'ssr.js',
libraryExport: 'default',
libraryTarget: 'commonjs2',
},
//±ÜÃâ°Ñnode_modulesÀïµÄ¿â¶¼´ò°ü½øÈ¥£¬´Ëssr js»áÖ±½ÓÔËÐÐÔÚnode¶Ë£¬
//ËùÒÔ²»ÐèÒª´ò°ü½ø×îÖÕµÄÎļþÖУ¬ÔËÐÐʱ»á×Ô¶¯´Ónode_modulesÀï¼ÓÔØ
externals: [nodeExternals()],
//...
}

È»ºóÔÚkoa app.js, requireËü£¬²¢ÇÒµ÷ÓÃSSRµÄ·½·¨:

//...koa app.js
//build³öÀ´µÄssr.js
const SSR = require('./build/node/ssr');
//preload all components on server side, ·þÎñ¶ËûÓж¯Ì¬¼ÓÔØ¸÷¸ö×é¼þ£¬ÌáǰÏȼÓÔØºÃ
SSR.preloadAll();
//ʵÀý»¯Ò»¸öSSR¶ÔÏó
const s = new SSR();
router.get('*', async (ctx) => {
//¸ù¾Ý·ÓÉ£¬äÖȾ²»Í¬µÄÒ³Ãæ×é¼þ
const rendered = s.render(ctx.url);

const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app">${rendered.html}</div>
<script type="text/javascript" src="/runtime.js"></script>
${rendered.scripts.join()}
<script type="text/javascript" src="/app.js"></script>
</body>
</html>
`;
ctx.body = html;
});
//...

ÒÔÉÏÊǸö¼òµ¥µÄʵÏÖReact SSRµ½koa web server, ΪÁËʹreact-loadableÖªµÀÄÄЩ×é¼þÔÚ·þÎñ¶ËäÖȾÁË£¬renderedÀïÃæµÄscriptsÊý×éÀïÃæ°üº¬ÁËSSR¹ýµÄ×é¼þ×é³ÉµÄ¸÷¸öscript±êÇ©£¬ÀïÃæµ÷ÓÃÁËSSR#generateBundleScripts()·½·¨£¬ÔÚ²åÈëʱÐèҪȷ±£ÕâЩscript±êÇ©ÔÚruntime.jsÖ®ºó((ͨ¹ý CommonsChunkPlugin À´³é³öÀ´))£¬²¢ÇÒÔÚapp bundle֮ǰ£¨Ò²¾ÍÊdzõʼ»¯µÄʱºòÓ¦¸ÃÒѾ­ÖªµÀ֮ǰµÄÄÄЩ×é¼þÒѾ­äÖȾ¹ýÁË£©¡£¸ü¶àreact-loadable·þÎñ¶ËÖ§³Ö£¬²Î¿¼ÕâÀï.

ÉÏÃæÎÒÃÇ»¹°Ñreact-routerµÄ·Óɶ¼µ¥¶À³é³öÈ¥ÁË£¬Ê¹µÃËü¿ÉÒÔÔËÐÐÔÚä¯ÀÀÆ÷¸ú·þÎñ¶Ë£¬ÒÔÏÂÊÇAppRoutes×é¼þ£º

//AppRoutes.js
import Loadable from 'react-loadable';
//...
const AsyncHello = Loadable({
loading: <div>loading...</div>,
loader: () => import('./Hello'),
})
function AppRoutes(props) {
<Switch>
<Route exact path="/hello" component={AsyncHello} />
<Route path="/" component={Home} />
</Switch>
}
export default AppRoutes
//È»ºóÔÚ App.js Èë¿ÚÖÐ
import AppRoutes from './AppRoutes';
// ...
export default () => {
return (
<Router>
<AppRoutes/>
</Router>
)
}

·þÎñ¶ËäÖȾµÄ³õʼ״̬

ĿǰΪֹ£¬ÎÒÃÇÒѾ­´´½¨ÁËÒ»¸öReact SPA£¬²¢ÇÒÄÜÔÚä¯ÀÀÆ÷¶Ë¸ú·þÎñ¶Ë¹²Í¬ÔËÐÐ??£¬ÉçÇø³ÆÖ®Îªuniversal app »òÕß isomophic app¡£µ«ÊÇÎÒÃÇÏÖÔÚµÄapp»¹ÓÐÒ»¸öÒÅÁôÎÊÌ⣬һ°ãÀ´ËµÎÒÃÇappµÄÊý¾Ý»òÕß״̬¶¼ÐèҪͨ¹ýÔ¶¶ËµÄapiÀ´Òì²½»ñÈ¡£¬Äõ½Êý¾ÝºóÎÒÃDzÅÄÜ¿ªÊ¼äÖȾ×é¼þ£¬·þÎñ¶ËSSRÒ²ÊÇÒ»Ñù£¬ÎÒÃÇÒª¶¯Ì¬µÄ»ñÈ¡³õʼÊý¾Ý£¬È»ºó²ÅÄÜÈÓ¸øReactÈ¥×öSSR£¬È»ºóÔÚä¯ÀÀÆ÷¶ËÎÒÃÇ»¹Òª³õʼ»¯¾ÍÄÜͬ²½»ñÈ¡ÕâЩSSRʱµÄ³õʼ»¯Êý¾Ý£¬±ÜÃâä¯ÀÀÆ÷¶Ë³õʼ»¯Ê±ÓÖÖØÐ»ñÈ¡ÁËÒ»±é¡£

ÏÂÃæÎÒÃǼòµ¥´Ógithub»ñȡһЩÏîÄ¿µÄÐÅÏ¢×÷ÎªÒ³Ãæ³õʼ»¯µÄÊý¾Ý, ÔÚkoaµÄapp.jsÖÐ:

//...
const fetch = require('isomorphic-fetch');
router.get('*', async (ctx) => {
//fetch branch info from github
const api = 'https://api.github.com/repos/jasonboy/wechat-jssdk/branches';
const data = await fetch(api).then(res => res.json());

//´«Èë³õʼ»¯Êý¾Ý
const rendered = s.render(ctx.url, data);

const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app">${rendered.html}</div>

<script type="text/javascript">window.__INITIAL_DATA__ = ${JSON.stringify(data)}</script>

<script type="text/javascript" src="/runtime.js"></script>
${rendered.scripts.join()}
<script type="text/javascript" src="/app.js"></script>
</body>
</html>
`;
ctx.body = html;
});

È»ºóÔÚÄãµÄHello×é¼þÖУ¬ÄãÐèÒªcheckwindowÀïÃæ£¨»òÕßÔÚAppÈë¿ÚÖÐͳһÅжϣ¬È»ºóͨ¹ýprops´«µ½×Ó×é¼þÖУ©ÊÇ·ñ´æÔÚwindow.__INITIAL_DATA__£¬Óеϰֱ½ÓÓÃÀ´µ±×ö³õʼÊý¾Ý£¬Ã»ÓеϰÎÒÃÇÔÚcomponentDidMountÉúÃüÖÜÆÚº¯ÊýÖÐÔÙÈ¥À´Êý¾Ý£º

export default class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {
//ÕâÀïÖ±½ÓÅжÏwindow£¬Èç¹ûÊǸ¸×é¼þ´«ÈëµÄ»°£¬Í¨¹ýpropsÅжÏ
github: window.__INITIAL_DATA__ || [],
};
}

componentDidMount() {
//ÅжÏûÓÐÊý¾ÝµÄ»°£¬ÔÙÈ¥ÇëÇóÊý¾Ý
//ÇëÇóÊý¾ÝµÄ·½·¨Ò²¿ÉÒÔ³é³öÈ¥£¬ÒÔÈÃä¯ÀÀÆ÷¼°·þÎñ¶ËÄÜͳһµ÷Ó㬱ÜÃâÖØ¸´Ð´
if (this.state.github.length <= 0) {
fetch('https://api.github.com/repos/jasonboy/wechat-jssdk/branches')
.then(res => res.json())
.then(data => {
this.setState({ github: data });
});
}
}

render() {
return (
<div>
<ul>
{this.state.github.map(b => {
return <li key={b.name}>{b.name}</li>;
})}
</ul>
</div>
);
}
}

ºÃÁË£¬ÏÖÔÚÈç¹ûÒ³Ãæ±»·þÎñ¶ËäÖȾ¹ýµÄ»°£¬ä¯ÀÀÆ÷»áÄõ½ËùÓÐäÖȾ¹ýµÄhtml, °üÀ¨³õʼ»¯Êý¾Ý£¬È»ºóͨ¹ýÕâЩSSRµÄÄÚÈÝÅäºÏ¼ÓÔØµÄjs£¬ÔÙ×é³ÉÒ»¸öÍêÕûµÄSPA£¬¾ÍÏñÒ»¸öÆÕͨµÄSPAÒ»Ñù£¬µ«ÊÇÎÒÃǵõ½Á˸üºÃµÄÐÔÄÜ£¬¸üºÃµÄSEO??¡£

ºÃÁË£¬ÒÔÉϾÍÊÇReact-SSR + KoaµÄ¼òµ¥Êµ¼ù£¬Í¨¹ýSSR£¬ÎÒÃǼÈÌáÉýÁËÐÔÄÜ£¬ÓֺܺõÄÂú×ãÁËSEOµÄÒªÇó£¬Best of the Both Worlds¡£

Ïà¹ØÎÄÕÂ

Éî¶È½âÎö£ºÇåÀíÀôúÂë
ÈçºÎ±àд³öÓµ±§±ä»¯µÄ´úÂë
ÖØ¹¹-ʹ´úÂë¸ü¼ò½àÓÅÃÀ
ÍŶÓÏîÄ¿¿ª·¢"±àÂë¹æ·¶"ϵÁÐÎÄÕÂ
Ïà¹ØÎĵµ

ÖØ¹¹-¸ÄÉÆ¼ÈÓдúÂëµÄÉè¼Æ
Èí¼þÖØ¹¹v2
´úÂëÕû½àÖ®µÀ
¸ßÖÊÁ¿±à³Ì¹æ·¶
Ïà¹Ø¿Î³Ì

»ùÓÚHTML5¿Í»§¶Ë¡¢Web¶ËµÄÓ¦Óÿª·¢
HTML 5+CSS ¿ª·¢
ǶÈëʽC¸ßÖÊÁ¿±à³Ì
C++¸ß¼¶±à³Ì

 
   
2280 ´Îä¯ÀÀ       27