前言
书接上文问题:【第二届青训营-寒假前端场】 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 的同步。
这样就可以达到两端渲染的目的
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:
参考
具体原理可以看这两篇文章: