在上一篇文章中咱们实现了一个简单的 hello world, 这一节咱们继续完善咱们的项目. 做为实用篇本篇将会添加 react-router reduxhtml
并经过 react-helmet 实现自定义的 meta 标签, 完善 SEO. 这个实在是不想写了. 下篇吧...前端
来到皇冠赌场的你们那确定是丈二的和尚, 摸不着头脑呀. 那么路由就应运而生了, 关于路由的原理建议你们看看这篇文章.node
若是你看了还回来了, 那说明仍是咱们澳门 XXXX 更加的有意思 😹.react
谈到赌场无非就是这老四样, 抓牌, 看牌, 洗牌, 码牌~ios
那么咱们就开始, 建立几个页面. 页面的代码结构以下图所示.git
为了便于各个 level 的小伙伴理解, 这里无耻的运用了拼音命名法. 代码改动在这里github
其次, 安装 react-router-dom 依赖, 并修改 App.jsx 和 client.js 文件, diff 在这里web
修改后的 App.jsx 文件面试
import React from 'react';
// 从 react-router-dom 引入基础组件
import { NavLink, Switch, Route } from 'react-router-dom';
// 引入皇冠赌场的页面
import Home from './Home.jsx';
import Zhuapai from './Zhuapai.jsx';
import Kanpai from './Kanpai.jsx';
import Xipai from './Xipai.jsx';
import Mapai from './Mapai.jsx';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div className="ssr-show">
<h1>欢迎来到澳门皇冠赌场</h1>
<NavLink to="/">首页</NavLink>
<NavLink to="/Zhuapai">抓牌</NavLink>
<NavLink to="/Kanpai">看牌</NavLink>
<NavLink to="/Xipai">洗牌</NavLink>
<NavLink to="/Mapai">码牌</NavLink>
<Switch>
<Route path="/" component={Home} />
<Route path="/Zhuapai" component={Zhuapai} />
<Route path="/Kanpai" component={Kanpai} />
<Route path="/Xipai" component={Xipai} />
<Route path="/Mapai" component={Mapai} />
</Switch>
</div>
);
}
}
复制代码
修改后的 client.js 为ajax
import React from 'react';
import ReactDOM from 'react-dom';
// 从 react-router-dom 里边导入 BrowserRouter 组件
import { BrowserRouter } from 'react-router-dom';
import App from './components/App.jsx';
// 包装一下 App 组件
ReactDOM.render(
<BrowserRouter> <App /> </BrowserRouter>,
document.getElementById('app'),
);
复制代码
目前为止, 路由就算是配完了. 执行 npm run build:client
后, 用浏览器打开 index.html 文件.
成功就在眼前, 可是不免有一点小小的障碍~
这个报错的大体意思就是, 本地的文件不能用 react-router, 那么咱们只能把它放到一个服务器上了. 仍是咱们的老伙伴 --- live-server
直接执行 live-server ./dist
浏览器打开 localhost:8080
咱们不难发现, 点击连接的时候浏览器地址栏有变化, 可是咱们并不能体验到从发牌到码牌的一条龙"服务"...
看了下 react-router 的 官方文档 原来咱们没有指定路径匹配必须得精准匹配. 那么咱们加上 exact
属性试试咧~
此时的代码 diff
到目前为止, 我咱们已经能够畅游澳门皇冠赌场了, 抓牌看牌洗牌码牌样样精通~
轻松搞定了客户端渲染的 react-router, 服务端渲染的话那就更加的简单了.
代码diff
最后的最后, 咱们执行一下 node index.js
, 浏览器打开 localhost:9999
经过 gif 咱们能发现, 咱们的服务端渲染是货真价实的服务端渲染了. 查看源代码的 html 字符串没有任何的问题了.(若是有样式该咋办呢 🤔)
细心的同窗可能发现了, 咱们每次点击连接的时候页面都会总体刷新. 这里就又到了那个经典的面试题前端:你要懂的单页面应用和多页面应用, 咱们的目的很简单, 只是须要 ssr 实现首屏的渲染, 以后就由客户端接管, 这样就结合了二者的优势. 需求是有了, 怎么实现咧~
聪明的同窗已经猜到, 只要咱们咱们在服务端渲染的页面中也引入客户端渲染生成的 bundle.js
文件是否是就 OK 了呢.那咱们就试试咯
修改 server.js
引入 koa-static
用于托管静态文件, 文件总体 diff 以下:
废话少扯, 继续 node index.js
, 浏览器打开 localhost:9999
搞起~
服务端渲染的路由配置, 这就完成了~
因为配置 redux 不是咱们的重点, 因此这里就很少说了, 简单配置一个 redux 开发环境. 代码 diff
有疑问的问题, 评论区见~
修改 server.js 文件以下:
import path from 'path';
import Koa from 'koa';
import Router from 'koa-router';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import serve from 'koa-static';
// 像客户端渲染同样导入 Provider 组件
import { Provider } from 'react-redux';
import App from './components/App.jsx';
import createStore, { init } from './store';
const app = new Koa();
const router = new Router();
const conf = {
PORT: 9999,
};
const generateHtmlStr = reactDom => `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">${reactDom}</div>
<script src="/dist/bundle.js"></script>
</body>
</html>
`;
router.get('*', (ctx) => {
const context = {};
const { url } = ctx.req;
// 初始化一个 store
const store = createStore();
// 手动触发一下 init
store.dispatch(init());
// 首先把 React 组件变成一个字符串
// eslint-disable-next-line
const rNode = renderToString(
// 把刚刚建立的 store 做为属性传给 Provider 组件
<Provider store={store}>
<StaticRouter location={url} context={context}>
<App />
</StaticRouter>
</Provider>,
);
// 而后替换 template 里边的内容
const domString = generateHtmlStr(rNode);
// 最后返回 html 字符串
ctx.body = domString;
});
app.use(serve(path.resolve(__dirname, '../')));
app.use(router.routes(), router.allowedMethods());
app.listen(conf.PORT, () => {
console.log(`The Server is listening on ${conf.PORT} now, enjoy`);
});
复制代码
代码 diff 以下:
仍是那一套, 先 node index.js
再浏览器打开 localhost:9999
~
浏览器执行结果以下图:
细心的同窗不难发现, 这个图片中抓牌的入口老是会闪动一下, 理论上讲咱们执行了 store.dispatch(init()); 证实了用户是已经登陆的用户, 因此应该是能够抓牌的才对. 这是问啥呢???
其实缘由很简单, 咱们的项目在首屏渲染完成之后就有客户端渲染接管了, 因此咱们应该在客户端接管的时候把以前服务端渲染的数据保留下来. 怎么搞呢?
bundle.js
的上方.这就完了, 经过下边的 gif 能够看出, 状态很好的保存下来了~
到了这里, 可能有同窗会问, 咱们来到皇冠赌场, 全局状态确定会灰常的多, 不该该只有一个登录状态. 鉴于此, 咱们扩充一下全局状态. 添加一个音乐列表. 一遍欢歌一遍看牌~
下来咱们在看牌页面添加一个音乐列表, 咱们听着音乐看着牌, 要是再吃着火锅那简直就是人生巅峰了.
import axios from 'axios';
export default () => axios('https://music.niubishanshan.top/api/v2/music/toplist')
.then(({ data }) => data);
// 在 mock.js 中咱们引用了高端的 ajax 请求库 axios. 那么就不得不 npm i axios -S 啦
复制代码
Header Home Zhuapai
到目前为止, 代码是 这样 的.
废话少说...
卧槽, 竟然很差使...
仔细看一下, 原来是 action 里边不能包含异步. 这个简单. 咱们升级一下.
而后...
有的时候, 咱们服务端渲染的首屏网页也须要从其余异步接口来获取初始化数据. 此时就须要吧 html 返回给前端前先去访问异步接口. 那么怎么办咧...
最后...
这里建议你们复习一下 polyfill 和 preset 的区别
反正我就这么干了一把
而后就...
是否是能够痛快的玩耍啦 😺~