S14-04 SSR-React18 SSR
[TOC]
原生实现
概述
React和Vue一样,除了支持开发SPA应用之外,其实也是支持开发SSR应用的。
在React中创建SSR应用,需要调用 ReactDOM.hydrateRoot() 函数,而不是ReactDOM.createRoot
- createRoot :创建一个Root,接着调用其 render 函数将App直接过载到页面上
- hydrateRoot() :创建水合Root ,是在激活的模式下渲染 App
服务端可用 ReactDOM.renderToString() 来进行渲染

搭建Node Server
依赖包:
- express
- 安装:
pnpm i express
- 安装:
- nodemon
- 安装:
pnpm i nodemon -g
- 安装:
- webpack
- webpack-cli
- webpack-node-externals
- 安装:
pnpm i webpack webpack-cli webpack-node-externals -D
- 安装:
搭建过程:
1、运行pnpm init 初始化package.json

2、创建一个express服务器

3、在package.json中编写npm脚本,运行node服务器

4、打包/src/server/index.js文件
打包配置
/config/webpack.server.js。target
打包命令,
--watch表示内容变化时会重新打包。
5、优化打包:使用 webpack-node-externals 在打包时排除node_modules中的包。
此时打包的js文件有900kb大小,需要优化。

6、测试打包后的js文件是否可以运行(OK)


搭建React18 SSR Server
依赖包:
- react
- 安装:
pnpm i react
- 安装:
- react-dom
- 安装:
pnpm i react-dom
- 安装:
- webpack-merge
- 安装:
pnpm i webpack-merge -D
- 安装:
- babel-loader
- @babel/preset-react
- @babel/preset-env
- 安装:
pnpm i babel-loader @babel/preset-react @babel/preset-env -D
- 安装:
搭建过程:
注意: react中不用调用SSR专用的方法(如vue中的createSSRApp())生成app,可以直接返回App。其他流程和vue基本一致。
1、编写app.jsx

2、在src/server/index.js中通过 renderToString() 方法将app实例转化为HTML字符串。并返回给前端。

6、打包项目:pnpm run build:server
7、运行打包后的项目:pnpm run start

8、此时页面可以展示,但是不能互动,页面中的按钮不起作用。

Hydration
安装的依赖项同前面一样
服务器端渲染页面 + 客户端激活页面,是页面有交互效果(这个过程称为:Hydration 水合)
Hydration的具体步骤如下:
1、在src/client/index.js中通过 hydrateRoot() 创建一个App实例并挂载到#root元素上。

2、创建配置文件webpack.client.js,并配置打包项。

3、创建打包脚本

4、打包项目:pnpm run build:client

5、在src/server/index.js的HTML模板中,引入client_bundle.js。JS文件部署在静态服务器中。
注意: 在HTML模板中引入${AppHtmlString}时,起前后不能有空格或换行,否则报错

6、运行项目:pnpm run start。此时页面中的JS代码已经激活。

合并配置
依赖包:
- webpack-merge:合并webpack配置。
- 安装:
pnpm i webpack-merge -D
- 安装:
base.config.js

server.config.js

client.config.js

集成Router
依赖包:
- react-router-dom
- 安装:
npm i react-router-dom( 默认会自动安装 react-router )
- 安装:
实现过程:
1、在src/router/index.js中创建一个路由实例。

2、在src/app.jsx中将routes转化为组件形式的路由并添加路由占位。

3、在src/server/index.js中挂载路由到app上。<App />需要用<StatciRouter>包裹。

4、在src/client/index.js中也挂载一遍路由。<App />需要用<BrowserRouter>包裹。

5、效果

集成ReduxToolkit
Redux Toolkit 是官方推荐的编写 Redux 逻辑的方法。
在前面我们学习Redux的时候应该已经发现,redux的编写逻辑过于的繁琐和麻烦。
并且代码通常分拆在多个文件中(虽然也可以放到一个文件管理,但是代码量过多,不利于管理);
Redux Toolkit包旨在成为编写Redux逻辑的标准方式,从而解决上面提到的问题;
在很多地方为了称呼方便,也将之称为“RTK”;
安装
npm install @reduxjs/toolkit react-reduxAPI
- configureStore({ reducer, middleware, devTools, ... }):
返回:store,包装createStore以提供简化的配置选项和良好的默认值。它可以自动组合 slice reducer,添加你提供的任何 Redux 中间件,redux-thunk默认包含,并启用 Redux DevTools Extension。- 参数
- reducer:``,Redux store 的根 reducer
- middleware:``,要使用的中间件数组
- devTools:``,是否启用开发工具(如 Redux DevTools),默认true
- preloadedState:``,初始状态
- enhancers:``,其他 store 增强器
- 返回
- store:``,返回的是一个 Redux store 实例,而不是一个类。因此无法创建多个 store 实例
- createSlice({ name, initialState, reducers,... }):
返回:reducerSlice,用于创建一个Redux reducer和action creator的集合- 参数
- name:
String,用于标识这个reducer的名称,action.type会根据name生成 - initialState:
any,表示这个reducer的初始状态 - reducers:
{ reducer,... },用于定义这个reducer的action creator和对应的reducer函数- reducer:
(state, action) => void,相当于之前的reducer函数
- reducer:
- 返回
- reducerSlice:``,返回一个reducer片段
- createAsyncThunk(typePrefix, payloadCreator, options? ):
返回:,用于创建一个异步action creator- 参数
- typePrefix:
String,用于标识这个异步action creator的类型前缀 - payloadCreator:
(arg, thunkAPI) => Promise,用于处理异步操作并返回一个Promise对象 - options?:
Object,用于配置异步action creator的一些选项- fulfilled:用于指定异步操作成功时的处理函数。
- rejected:用于指定异步操作失败时的处理函数。
- pending:用于指定异步操作进行中时的处理函数。
- dispatchCondition:用于指定在什么条件下才会dispatch这个action的函数。
- condition:用于指定在什么条件下才会调用
payloadCreator函数的函数。 - typeSuffixes:用于指定异步action creator的类型后缀的对象。
- serializeError:用于指定如何序列化异步操作的错误信息的函数。
集成过程
1、创建store:configureStore()
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './features/counter'
import homeReducer from './features/home'
+ const store = configureStore({
+ reducer: {
+ counter: counterReducer,
+ home: homeReducer
+ }
+ })
export default store2、创建reducer片段:createSlice()
import { createSlice } from "@reduxjs/toolkit";
+ const counterSlice = createSlice({
+ name: 'counter',
+ initialState: {
+ counter: 100
+ },
+ reducers: {
+ addCounter(state, action) {
+ state.counter += action.payload
+ },
+ subCounter(state, action) {
+ state.counter -= action.payload
+ }
+ }
+ })
+ export default counterSlice.reducer3、在client中结合redux和react组件

4、在server中结合redux和react组件

5、在组件中使用 useSelector() 获取store中的数据

6、在组件中使用 useDispatch() 修改store中的数据

7、效果

异步操作
1、在home.ts中发送异步请求
+ export const fetchHomeMultidataAction = createAsyncThunk('home/multidata', async () => {
const res = await axios.get('http://123.207.32.32:8000/home/multidata')
return res.data.data
})
const homeSlice = createSlice({
name: 'home',
initialState: {
banners: [],
recommends: []
},
+ extraReducers(builder) {
+ builder
+ .addCase(fetchHomeMultidataAction.fulfilled, (state, { payload }) => {
state.banners = payload.banner.list
state.recommends = payload.recommend.list
})
+ .addCase(fetchHomeMultidataAction.rejected, (state) => {
console.log('fetchHomeMultidataAction.rejected')
})
}
})2、在组件中调用 fetchHomeMultidataAction()
