使用 React + Koa 從零開始一步一步的帶你開發一個 36kr SSR 案例(一)

前言

本項目源碼地址 github.com/zwmmm/react… 喜歡的給個star鼓勵下做者,有問題能夠提issuecss

也許你看過其餘的ssr教程都會先說一說spa和ssr的區別以及優缺點,可是我相信能點進來看的小夥伴們確定是對這兩個概念有過了解的,也無需我在這裏多費口舌。不懂的能夠直接看這裏html

那麼咱們就直接進入正題了!!!前端

搭建目錄結構

首先咱們建立一個react-ssr文件夾, 執行git init初始化git倉庫,添加以下目錄和文件。node

.
|-- app
|-- build
|-- server
|-- template
|-- package.json
|-- README.md
|-- .gitignore
複製代碼

.gitignore忽略文件react

node_modules
.cache
.idea
複製代碼

webpack的配置

安裝webpack

npm install --save-dev webpack webpack-cli
複製代碼

推薦使用 --save-dev 安裝,由於如今webpack版本不少,全局安裝不利於各個項目管理。webpack

配置react環境

首先咱們明確下目標,要想運行react的代碼,首先將react中的jsx編譯成js代碼。git

先在app下建立入口文件main.jses6

|-- app
|   |-- main.js
複製代碼

template下建立模板文件app.htmlgithub

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>demo</title>
</head>
<body>
    <div id="app"></div>
</body>
</html>
複製代碼

build文件夾中建立utils.js文件。先寫一些公共的方法。web

const path = require('path');

exports.resolve = (...arg) => path.join(__dirname, '..', ...arg);
複製代碼

build文件夾中建立webpack.base.config.js文件

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { resolve } = require('./utils');

module.exports = {
    entry: resolve('app/main.js'),
    output: {
        path: resolve('dist'),
        filename: 'index.js'
    },
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                // 只編譯app文件夾下的文件
                include: resolve('app'),
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            '@babel/preset-env',
                            '@babel/preset-react',
                        ],
                    }
                }
            },
        ]
    },
    resolve: {
        // 設置路徑別名
        alias: {
            '@': resolve('app'),
        },
        // 文件後綴自動補全, 就是你import文件的時候若是沒寫後綴名就會優先找下面這幾個
        extensions: [ '.js', '.jsx' ],
    },
    // 第三方依賴,能夠寫在這裏,不打包
    externals: {},
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: resolve('template/app.html')
        })
    ]
}
複製代碼

安裝下上面用到的包

npm i -D @babel/cli @babel/core @babel/preset-env @babel/preset-react babel-loader html-webpack-plugin
複製代碼

簡單說下這幾個配置的做用

  • entry 指定入口
  • output 設置出口並肯定輸出的文件名稱
  • rules 配置loader
  • babel 編譯代碼,將代碼轉成瀏覽器能夠運行的代碼
  • HtmlWebpackPlugin 自動生成html的插件

若是不熟悉babel的同窗能夠看這篇文章,不過我使用了babel7 因此在包名上會有不一樣,新版的babel統一有@babel前綴

配置好了就須要咱們寫點react代碼測試下啦

首先下載react相關的資源包

npm i --save react react-dom
複製代碼

app/main.js編寫以下代碼

import React from 'react';
import { render } from 'react-dom';

function App() {
    return <div>Hello React</div>
}

render(<App/>, document.getElementById('app'));
複製代碼

package.json中增長一條script命令

{
  "scripts": {
    "start": "webpack --config build/webpack.base.config.js"
  },
}
複製代碼

執行npm start 打開dist/index.html就能夠查看效果,正確狀況下會顯示Hello React

到此咱們就已經完成咱們的第一階段,能夠編寫react代碼

配置開發環境

上面咱們說了如何編譯react代碼,可是在咱們實際開發中不可能每次修改代碼都要npm start,因此在上面的基礎上配置一個dev環境

在配置dev環境以前先介紹下webpack-dev-server,這個插件能夠在本地啓動一個本地服務,而且提供了很是豐富的功能,例如熱更新,接口代理。首先咱們安裝下

npm i -D webpack-dev-server
複製代碼

build下新建webpack.dev.config.js

const merge = require('webpack-merge');
const webpack = require('webpack');
const baseConfig = require('./webpack.base.config');

module.exports = merge(baseConfig, {
    // 用於調試, inline-source-map模式效率比較高, 因此在dev模式下推薦使用這個
    devtool: 'inline-source-map',
    mode: 'development',
    // 設置dev服務器
    devServer: {
        // 設置端口號,默認8080
        port: 8000,
    },
    plugins: [
        // 在js中注入全局變量process.env用來區分環境
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: JSON.stringify('development'),
            }
        }),
    ],
})
複製代碼

安裝下webpack-merge

npm i -D webpack-merge
複製代碼

簡單說下上面的配置

  • 使用webpack-merge複用以前的配置
  • 配置devServer
  • 注入process.env全局變量區分環境

最後咱們在修改下啓動命令

{
  "scripts": {
    "start": "webpack-dev-server --hot --config build/webpack.dev.config.js"
  },
}
複製代碼

如今咱們執行下npm start 瀏覽器打開localhost:8000訪問,並嘗試修改main.js中的react代碼,不刷新瀏覽器是否會自動更新

如今咱們的webpack已經能夠支持簡單的開發了,可是這還遠遠不夠,在編寫前端代碼時,咱們還會接觸到cssimage、等其餘文件的使用,因此須要增強下webpack的配置

module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                // 只編譯app文件夾下的文件
                include: resolve('app'),
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            '@babel/preset-env',
                            '@babel/preset-react',
                        ],
                    }
                }
            },
+ {
+ test: /\.html$/,
+ include: resolve('app'),
+ loader: 'html-loader'
+ },
+ {
+ test: /\.less/,
+ include: resolve('app'),
+ use: [
+ 'style-loader',
+ 'css-loader',
+ 'less-loader'
+ ]
+ },
+ {
+ test: /\.(png|jpg|gif|svg)$/,
+ loader: `url-loader?limit=1000`
+ },
+ {
+ test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
+ loader: `file-loader`
+ },
+ ]
    },
複製代碼

下載須要的loader以及less

npm i -D html-loader style-loader css-loader less-loader url-loader file-loader less
複製代碼

通過下面的配置咱們就能夠在代碼中作以下的操做

import img from './xxx.png'
import 'xxx.less'
import html from 'xxx.html'
複製代碼

那麼接下來咱們就給咱們的react豐富一下代碼

首先在app文件夾下新建style static文件夾分別存放css文件和靜態資源,

新增index.lesstimg.png

#app {
    text-align: center;
    color: deepskyblue;
}
.logo {
    width: 500px;
}
複製代碼

而後修改main.js

import React from 'react';
import { render } from 'react-dom';
import './style/index.less';
import logo from './static/timg.jpg'

function App() {
    return <div> <h1>Hello React !!!</h1> <img src={ logo } className="logo"/> </div> } render(<App/>, document.getElementById('app')); 複製代碼

最終的效果

這裏可能會有同窗會有一個疑問, 圖片爲何直接使用<img src="./static/time.png" className="logo"/>這樣引入?其實很好解釋,咱們的網站是訪問的webpack-dev-server啓動的服務,若是沒有使用import引入圖片,則在服務器中就不會存在這個圖片。而import圖片的時候 首先會找到對應的圖片資源存到服務器上, 而且生成一個文件路徑供咱們訪問。

使用Koa搭建Node服務

react的部分咱們先告一段落,後面還會繼續說到react-router redux,接下來咱們說下服務端,也算是正式講點ssr的東西

首先在這裏提一嘴,ssr和普通的spa頁面最大的區別在於,咱們是直接將完整的html返回給瀏覽器的。

話很少說,直接開工!!!

先下載koa

npm i -S koa
複製代碼

建立server/app.js文件

const Koa = require('koa');

const app = new Koa();

app.use(ctx => {
    ctx.body = '<div>Hello Koa<div/>'
})

app.listen(9000, () => {
    console.log(`node服務已經啓動, 請訪問localhost:9000`)
})
複製代碼

添加一條script命令

"server": "node server/app.js"
複製代碼

運行npm run server並訪問localhost:9000

這時候就能夠看到Hello Koa,其實這就是一個最基本的直出服務,如今讓咱們想想,若是代碼能夠寫成這樣

app.use(ctx => {
- ctx.body = '<div>Hello Koa<div/>'
+ ctx.body = <App/>
    })
複製代碼

直接返回一個react組件,那不就是咱們要的react ssr?

固然上面的代碼直接這麼執行確定是會報錯,不過react給咱們提供了renderToString方法,將組件轉成字符串。這樣咱們就能夠實現渲染組件了!!!

來,咱們改良下上面的代碼,讓node支持jsx語法

先建立server/index.js,使用@babel/register在node運行時候編譯咱們的jsx代碼以及es6語法

安裝@babel/register

npm i -S @babel/register
複製代碼
require('@babel/register')({
    presets: [
        '@babel/preset-react',
        '@babel/preset-env'
    ],
});
require('./app.js');
複製代碼

修改script命令

- "server": "node server/app.js"
+ "server": "node server/index.js"
複製代碼

重構app.js

由於前面使用了babel編譯了代碼,因此可使用es6的模塊化

// jsx編譯以後會用到React對象, 因此須要引入
import React from 'react';
import Koa from 'koa';
import { renderToString } from "react-dom/server";

const app = new Koa();

const App = () => <div>Hello Koa SSR</div>

app.use(ctx => {
    ctx.body = renderToString(<App/>);
})

app.listen(9000, () => {
    console.log(`node服務已經啓動, 請訪問localhost:9000`)
})
複製代碼

如今咱們已經完成了最簡單的react ssr,下一步咱們將加上路由,實現對應的路由顯示對應的組件

SSR下的路由

看完上面的章節,大夥是否是想說,ssr是實現了,可是好像和我得前端部分並無關聯起來啊,我在前端寫的組件應該怎麼在Node中去使用呢?下面我在路由這個篇章就會將前端和Node關聯起來說,讓你們知道頁面究竟是怎麼渲染出來的。

在開始講以前我仍是得先和你們說說傳統的spa頁面路由是怎麼配置的,下面就以history模式爲例

首先咱們從瀏覽器輸入url,無論你的url是匹配的哪一個路由,後端通通都給你index.html,而後加載js匹配對應的路由組件,渲染對應的路由。

那咱們的ssr路由是怎麼樣的模式呢?

首先咱們從瀏覽器輸入url,後端匹配對應的路由獲取到對應的路由組件,獲取對應的數據填充路由組件,將組件轉成html返回給瀏覽器,瀏覽器直接渲染。當這個時候若是你在頁面中點擊跳轉,咱們依舊仍是不會發送請求,由js匹配對應的路由渲染

文字看懵的咱們直接看圖

因此咱們須要同時配置前端路由以及後端路由

那一步步來,咱們先配置前端路由,前端路由使用react-router,若是不會使用react-router的同窗能夠看下我寫的這篇入門文章

下載react-router

npm i -S react-router-dom
複製代碼

新建app/router.js

import { Link, Switch, Route } from 'react-router-dom';
import React from 'react';

const Home = () => (
    <div> <h1>首頁</h1> <Link to="/list">跳轉列表頁</Link> </div>
)

const list = [
    'react真好玩',
    'koa有點意思',
    'ssr更有意思'
]

const List = () => (
    <ul> { list.map((item, i) => <li key={ i }>{ item }</li>) } </ul>
)

export default () => (
    <Switch>
        <Route exact path="/" component={ Home }/>
        <Route exact path="/list" component={ List }/>
    </Switch>
)
複製代碼

修改main.js

import React from 'react';
import { render } from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import Router from './router'

render(
    <BrowserRouter> <Router/> </BrowserRouter>,
    document.getElementById('app')
);
複製代碼

執行npm start 訪問localhost:8000

ok,前端路由就這麼簡單的配置好了,如今若是你跳轉到列表頁,而後刷新頁面就會提示404這是由於咱們的dev-server沒有匹配上對應的路由,那麼接下來咱們就來配置服務端路由來解決這個問題,而且實現ssr

服務端路由咱們使用koa-router

先下載 npm i -S koa-router

新建server/router/index.js

import Router from 'koa-router';
import RouterConfig from '../../app/router';
import { StaticRouter } from 'react-router-dom';
import { renderToString } from "react-dom/server";
import React from 'react';

const routes = new Router();

routes.get('/', (ctx, next) => {
    ctx.body = renderToString(
        <StaticRouter location={ctx.url}> <RouterConfig/> </StaticRouter>
    )
    next();
})

routes.get('/list', (ctx, next) => {
    ctx.body = renderToString(
        <StaticRouter location={ctx.url}> <RouterConfig/> </StaticRouter>
    )
    next();
})

export default routes;
複製代碼

一下看不懂不要緊,聽我來解釋

首先咱們用koa-router註冊了/ /list 兩個路由,而且使用renderToString將組件轉成html

那這個StaticRouter是幹嗎的呢?和BrowserRouter有什麼區別?其實很簡單,在瀏覽器上咱們可使用js獲取到location,可是在node環境卻獲取不到,因此react-router提供了StaticRouter來讓咱們本身設置location

如今你也許會有另一個疑問,這兩個路由設置寫的代碼不是都同樣的麼,爲何還要去區分路由?這是應爲在生成html以前咱們還須要獲取對應的數據,因此必需要分開。後面我會繼續講ssr如何處理數據

接下來咱們改造下app.js

import Koa from 'koa';
import routes from './router';

const app = new Koa();

app.use(routes.routes(), routes.allowedMethods());

app.listen(9000, () => {
    console.log(`node服務已經啓動, 請訪問localhost:9000`)
})
複製代碼

啓動npm run server 訪問localhost:9000

如今咱們的localhost:9000 localhost:8000 均可以瀏覽了,正好大家能夠對比下兩種渲染方式。

ok,心細的朋友可能發現了localhost:9000下的頁面點擊跳轉是刷新頁面的,並非單頁面跳轉。這是由於咱們返回的html裏面根本就沒有攜帶js,因此跳轉路由固然是直接發生跳轉了啊,而且返回的html也是不完整的,如今咱們就給咱們的內容添加一個html模板

新建模板template/server.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>36氪_讓一部分人先看到將來</title>
    <link href="//36kr.com/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon">
</head>
<body>
    <div id="app">{{ html }}</div>
    <script src="http://localhost:8000/index.js"></script>
</body>
</html>
複製代碼

這裏咱們加載localhost:8000服務下的inedx.js,其實你能夠吧webpack-dev-server想象成靜態資源服務器了,這樣咱們的靜態資源在你的開發階段就能夠實時更新。

而後咱們給ctx對象擴展一個render方法,用來渲染html

import fs from 'fs';
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router-dom'
import RouterConfig from '../app/router'
import React from 'react';
import path from 'path';

// 匹配模板中的{{}}
function templating(props) {
    const template = fs.readFileSync(path.join(__dirname, '../template/server.html'), 'utf-8');
    return template.replace(/{{([\s\S]*?)}}/g, (_, key) => props[ key.trim() ]);
}

export default function(ctx, next) {
    try {
        ctx.render = () => {
            const html = renderToString(
                <StaticRouter location={ ctx.url }> <RouterConfig/> </StaticRouter>
            );
            const body = templating({
                html
            });
            ctx.body = body;
        }
    }
    catch (err) {
        ctx.body = templating({ html: err.message });
    }
    ctx.type = 'text/html';
    // 這裏必須是return next() 否則異步路由是404
    return next();
}
複製代碼

而後在app.js中加載上面寫的中間件

import Koa from 'koa';
    import routes from './router';
+ import templating from './templating'
    
    const app = new Koa();
    
+ app.use(templating);
    app.use(routes.routes(), routes.allowedMethods());
    
    app.listen(9000, () => {
        console.log(`node服務已經啓動, 請訪問localhost:9000`)
    })
複製代碼

最後咱們來改造下路由

import Router from 'koa-router';
import React from 'react';

const routes = new Router();

routes.get('/', (ctx, next) => {
    ctx.render();
    next();
})

routes.get('/list', (ctx, next) => {
    ctx.render();
    next();
})

export default routes;
複製代碼

重啓你的localhost:9000看看如今跳轉list是否是就不會再刷新頁面了。

到這裏咱們的路由就算配置完成了。相信你們對ssr也有必定的瞭解了,可是還不夠,目前咱們渲染的都是靜態頁面,也就是寫死的,而實際業務確定是根據數據渲染出來的,以前的spa頁面咱們會在組件中去發送請求獲取數據渲染,但咱們的ssr確定不能這樣作,因此得在生成html這一步獲取數據,那數據又該怎麼傳進組件內呢?以及先後端數據怎麼作到同步呢?下一個章節咱們就講講ssr的數據請求

SSR中的數據請求

react中操做數據無非兩種方式stateprops,咱們在node中確定是沒辦法給組件設置state的,因此只能經過props傳進去,而且咱們的數據還要作到先後端同步,否則你就光渲染出了html,數據沒給前端這樣也不行啊。而redux恰好知足這兩點需求。

既然要用redux那就得先從前端開始了啊,不熟悉redux的朋友建議先了解下基本概念

下載npm i redux react-redux -S

新建目錄

|-- app
|   |-- redux
|   |   |-- reducers
|   |   |-- store
複製代碼

先建立reducers

// reducers/home.js
const defaultState = {
    title: 'Hello Redux'
}

export default function(state = defaultState , action) {
    switch (action.type) {
        default:
            return state
    }
}
複製代碼
// reducers/list.js
const defaultState = {
    list: [
        'react真好玩',
        'koa有點意思',
        'ssr更有意思'
    ]
}

export default function(state = defaultState , action) {
    switch (action.type) {
        default:
            return state
    }
}
複製代碼

合併reducers

// reducers/index.js
import home from './home';
import list from './list';
import { combineReducers  } from 'redux';

// 其實就是把分散的reducers給合併了
export default combineReducers({
    home,
    list,
})
複製代碼

接下來建立store

import { createStore } from 'redux';
import reducers from '../reducers';

/** * 爲何寫成函數? * 由於咱們在前端和後端都須要去進行初始化store因此這裏封裝一個工廠函數 * @param data * @returns {*} */
export default data => createStore(reducers, data);
複製代碼

而後將store注入到組件中

// main.js
+ import { Provider } from 'react-redux';
+ import createStore from './redux/store/create';

+ const store = createStore();

render(
+ <Provider store={store}>
        <BrowserRouter>
            <Router/>
        </BrowserRouter>
+ </Provider>,
    document.getElementById('app')
);
複製代碼

page從路由中抽離出來

// pages/home.js
import { Link } from 'react-router-dom';
import React from 'react';
import { connect } from 'react-redux';

const Home = props => (
    <div> <h1>{ props.title }</h1> <Link to="/list">跳轉列表頁</Link> </div>
)

/** * 經過connect將redux中的數據傳遞進入組件 */
function mapStateTpProps(state) {
    return { ...state.home };
}

export default connect(mapStateTpProps)(Home)
複製代碼
// pages/list.js
import React from 'react';
import { connect } from 'react-redux';

const List = props => (
    <ul> { props.list.map((item, i) => <li key={ i }>{ item }</li>) } </ul>
)

/** * 經過connect將redux中的數據傳遞進入組件 */
function mapStateTpProps(state) {
    return { ...state.list };
}

export default connect(mapStateTpProps)(List)
複製代碼

最後修改下路由

import { Switch, Route } from 'react-router-dom';
import React from 'react';
import Home from './pages/home';
import List from './pages/list';

export default () => (
    <Switch>
        <Route exact path="/" component={ Home }/>
        <Route exact path="/list" component={ List }/>
    </Switch>
)
複製代碼

好了,最基本的redux已經完成,如今咱們已經將數據從組件內部提取到了redux來管理,接下來咱們實如今node中填充數據。

其實這一步很是簡單,只要修改下templating就能夠,直接看代碼

import fs from 'fs';
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router-dom'
import RouterConfig from '../app/router'
import React from 'react';
import path from 'path';
+ import { Provider } from 'react-redux';
+ import createStore from '../app/redux/store/create';

// 匹配模板中的{{}}
function templating(props) {
    const template = fs.readFileSync(path.join(__dirname, '../template/server.html'), 'utf-8');
    return template.replace(/{{([\s\S]*?)}}/g, (_, key) => props[ key.trim() ]);
}

export default function(ctx, next) {
    try {
+ ctx.render = (data = {}) => {
+ const store = createStore(data);
            const html = renderToString(
+ <Provider store={ store }>
                    <StaticRouter location={ ctx.url }>
                        <RouterConfig/>
                    </StaticRouter>
+ </Provider>
            );
            const body = templating({
                html
            });
            ctx.body = body;
        }
    }
    catch (err) {
        ctx.body = templating({ html: err.message });
    }
    ctx.type = 'text/html';
    // 這裏必須是return next() 否則異步路由是404
    return next();
}
複製代碼

而後咱們在調用ctx.render的時候將數據當作參數傳入就能夠了

import Router from 'koa-router';
import React from 'react';

const routes = new Router();

routes.get('/', (ctx, next) => {
    ctx.render({
        home: {
            title: '我是從node中獲取的數據'
        }
    });
    next();
})

routes.get('/list', (ctx, next) => {
    ctx.render({
        list: {
            list: [
                '我是從node中獲取的數據',
                '感受還不錯',
                '測試成功',
            ]
        }
    });
    next();
})

export default routes;
複製代碼

重啓npm run server 刷新下localhost:9000看看效果

誒,不對啊,是否是看到了,頁面一開始是正確的,而後又被從新覆蓋了?這是由於咱們加載了index.js他又從新初始化store,因此會產生這樣的問題。

那怎麼解決?還記得剛開始說的先後端數據同步麼?只要我把node用到的數據傳給前端,前端基於這個數據去初始化store這樣不就能夠了?

怎麼把數據傳給前端?很簡單,直接把store注入到window上就行。

先修改下咱們的模板server.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>36氪_讓一部分人先看到將來</title>
    <link href="//36kr.com/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon">
</head>
<body>
<div id="app">{{ html }}</div>
+ <script>
+ window.__STORE__ = {{ store }}
+ </script>
<script src="http://localhost:8000/index.js"></script>
</body>
</html>
複製代碼

改下templating

ctx.render = (data = {}) => {
    const store = createStore(data);
    const html = renderToString(
        <Provider store={ store }>
            <StaticRouter location={ ctx.url }>
                <RouterConfig/>
            </StaticRouter>
        </Provider>
    );
    const body = templating({
        html,
+ store: JSON.stringify(data, null, 4),
    });
    ctx.body = body;
}
複製代碼

最後前端獲取store

+ const defaultStore = window.__STORE__ || {}
- const store = createStore();
+ const store = createStore(defaultStore);

render(
    <Provider store={store}>
        <BrowserRouter>
            <Router/>
        </BrowserRouter>
    </Provider>,
    document.getElementById('app')
);
複製代碼

重啓npm run server 刷新下localhost:9000是否是完美了

最後補充一點關於api請求的點

由於一個頁面多是由node直出的,也有多是js加載的,因此咱們還須要在每一個組件的componentDidMount中去分析有沒有事先注入過store,來判斷是否須要請求,以下面的僞代碼。

componentDidMount() {
    const { news, fetchHome } = this.props;
    news.length || fetchHome();
}
複製代碼

其實到這裏咱們的ssr實現原理已經講完了,接下來的章節我會帶你們完成一個36kr的案例,想本身動手直接開擼的同窗也能夠直接看個人react-ssr-36kr源碼,那若是你對redux以及koa不是很熟悉的同窗則能夠繼續看個人下篇文章,下篇文章會帶你們進行實戰開發以及build發佈線上環境的配置。

點擊進入下篇教程

相關文章
相關標籤/搜索