在上一篇文章中咱們實現了一個簡單的 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 的區別
反正我就這麼幹了一把
而後就...
是否是能夠痛快的玩耍啦 😺~