React SSR 初实践(二)

307 阅读3分钟

前言

书接上文问题:【第二届青训营-寒假前端场】 React SSR 初实践(一)

1.如何在同构中引入路由

原理:

在 React SSR 项目中需要实现两端路由

客户端路由是用于支持用户通过点击链接的形式跳转页面

服务器端路由是用于支持用户直接从浏览器地址栏中访问页面。

客户端和服务器端共用一套路由规则。

如何实现:

客户端照常编写,用的是 BrowserRouter 包裹路由组件

//client/index.js
hydrate(
        <BrowserRouter>
            <App/>
        </BrowserRouter>
   ,document.getElementById('root'))
​
​

服务端用StaticRouter 包裹路由组件

// server/index.js
const content = renderToString(
            <StaticRouter  location={url}>
                <App/>          
        </Provider>
    );
///router/config.js
import Home from "../containers/components/Home";
import User from "../containers/components/User";
import React from "react";
​
export const routes=[
    {
        name:'Home',
        path:'/',
        component: <Home/>
    },   {
        name:'User',
        path:'/user',
        component: <User/>
    },
]
​
​
//App.js
import React from 'react';
import {Routes, Route, NavLink} from 'react-router-dom'
import {routes} from '../router/config'export const App =()=> {
​
    return <>
        <Routes>
            {routes.map(route=>{
                return <Route path={route.path} key={route.name} element={route.component} />
            })}
        </Routes>
        </>
}
export default App

差异:

StaticRouter 是无状态的,服务端渲染的时候路由是无状态的,所以要用StaticRouter,而BrowserRouter使用 HTML5 提供的 history API (pushState, replaceState 和 popstate 事件) 来保持 UI 和 URL 的同步。

这样就可以达到两端渲染的目的

image.png

image.png

2.如何在同构中引入状态管理

原理:

当使用带有服务器渲染的 Redux 时,我们还必须在响应中发送应用程序的状态,以便客户端可以将其用作初始状态。这很重要,因为如果我们在生成 HTML 之前预加载任何数据,我们希望客户端也可以访问这些数据。否则,客户端生成的标记将与服务器标记不匹配,客户端将不得不再次加载数据。

我们要做的事

  • 在每个请求上创建一个全新的 store 存储实例;
  • 根据业务执行dispatch
  • state拉出store
  • 然后将state传递给客户端。

服务器端的store,只有一个作用,就是作为客户端的store的初始状态,使其不需要重新修改客户端的store,实现同构.

实现:

//store/index.js
import {createStore} from 'redux'
import {reducer} from './reducer'export function getClientStore() {
//  服务端将属性挂载到客户端的window上,然后客户端根据window对象初始化store,使得客户端store数据与服务端store数据一致
    let initState = window.context.state;
    return createStore(reducer,initState);
}
export function getServerStore() {
    return createStore(reducer);
}
​

服务端将属性挂载到客户端的window上,然后客户端根据window对象初始化store,使得客户端store数据与服务端store数据一致。

export function getTemplate(url,store) {
    const content = renderToString(
        <Provider store={store}>
            <StaticRouter  location={url}>
                <App/>
            </StaticRouter>
        </Provider>
    );
    return `
   <html>
     <head>
       <title>hello</title>
     </head>
     <body>
      <div id="root">${content}</div>
     </body>
     <script>
        window.context = {
            state: ${JSON.stringify(store.getState())}
          } 
    </script>
     <script  src="./client.js"></script>
   </html>
 `;
}
​
//server.js
  app.use(async (ctx) => {
    const url=ctx.request.url;
    if(url.includes('.')){
        ctx.body='';
        return false
    }
    const store=getServerStore()
    //异步修改数据,更改store状态
    const {data}=  await axios.get('http://localhost:4000')
    store.dispatch(modifyUserAction(data.name,data.age))
    ctx.body = getTemplate(url,store)
})

通过 植入script标签,改变客户端的window对象,获取服务端的store状态。

github:

react-ssr-demo

参考

具体原理可以看这两篇文章

【长文慎入】一文吃透 React SSR 服务端渲染和同构原理

从头开始,彻底理解服务端渲染原理(8千字汇总长文)

React原生实现SSR(二十)

Redux文档