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()
