±à¼ÍƼö: |
±¾ÎÄÀ´×ÔÍøÂ磬±¾ÎÄÖ÷Òª½éÉÜÁËÔÚÒ»¸ö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¡£
|