原理实践 SSR中redux的使用
原理实践 SSR中redux的使用
文章目录
- 原理实践 SSR中redux的使用
- @reduxjs/toolkit 主要API
- 快速上手
- 创建
- 使用
- redux在SSR中的使用
安装
yarn add @reduxjs/toolkit
@reduxjs/toolkit 主要API
●configureStore(): 包装createStore以提供简化的配置选项和良好的默认值。它可以自动组合你的 slice reducer,添加你提供的任何 Redux 中间件,thunk中间件默认包含,并启用 Redux DevTools Extension。
●createReducer():这使您可以为 case reducer 函数提供操作类型的查找表,而不是编写 switch 语句。此外,它自动使用该immer库让您使用普通的可变代码编写更简单的不可变更新,例如state.todos[3].completed = true.
●createAction():为给定的动作类型字符串生成动作创建函数。该函数本身已toString()定义,因此可以使用它来代替类型常量。
●createSlice():接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的动作创建者和动作类型。
●createAsyncThunk: 接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected基于该承诺分派动作类型的 thunk
●createEntityAdapter: 生成一组可重用的 reducer 和 selector 来管理 store 中的规范化数据
快速上手
创建
创建目录,按照规范或自我习惯进行进行创建
store ├─ index.js └─ slices├─ home.js└─ personal.js
根据不同的业务创建不同的仓库片端
// store/slices/home import { createSlice,createAsyncThunk } from "@reduxjs/toolkit";// 初始值 const initialState = {articles:[],age:18 }// 异步的action使用createAsyncThunk // 第一个参数,用于生成额外Redux action常量的字符串,表示异步请求的生命周期: // 第二个参数是一个回调函数,返回值将被作为payload export const fetchHomeData = createAsyncThunk('home/fetch', async (dispath) => {// 模拟假数据const data = await new Promise((resolve,reject) => {setTimeout(() => {resolve({articles:[{id:1,title:'文章标题1',content:'文章内容1'},{id:2,title:'文章标题2',content:'文章内容2'}]})},2000)})return data })const homeSlice = createSlice({name:'homeSlice', // 类似于命名空间,(取个名字)initialState, // 初始状态reducers:{changeAge(state,action){state.age = action.payload}},// 针对异步action做的监听处理// 1、fulfilled 成功之后需要做的操作// 2、pending 加载时需要做的操作// 3、rejected失败后需要做什么处理extraReducers:(builder) => {builder.addCase(fetchHomeData.fulfilled, (state,action) => {state.articles = action.payload && action.payload.articles; })} })export const {changeAge} = homeSlice.actions export default homeSlice.reducer
创建store
// store/index import { configureStore } from '@reduxjs/toolkit' import homeReducer from './slices/home' import personalReducer from "./slices/personal"const store = configureStore({reducer:{home:homeReducer,personal:personalReducer} })export default store
嵌套在组件较外层(前后端路由都需嵌套)
// 服务端const content = ReactDOMServer.renderToString(<Provider store={store}><StaticRouter location={req.url}><RoutesList/></StaticRouter></Provider>)
// 客户端 hydrateRoot(document.querySelector('#root'),(<Provider store={store}><BrowserRouter><RoutesList/></BrowserRouter></Provider>) )
使用
import React, { useEffect } from "react";
import {useSelector,useDispatch} from "react-redux"
import {fetchHomeData,changeAge} from "../store/slices/home"const Home = () => {const dispath = useDispatch()const homeData = useSelector((state) => state.home) // 获取到home片段的数据useEffect(() => {dispath(fetchHomeData()) // 调用异步action},[])console.log(homeData)const handleClick = () => {dispath(changeAge(5)) // 调用actionconsole.log('点击')}return(<div><h1>首页</h1> <ul>{homeData?.articles.map((article) => {return(<li key={article.key}><p>{article?.title}</p><p>{article?.content}</p></li>)})}</ul><button onClick={handleClick}>点我</button></div>)
}export default Home
redux在SSR中的使用
以上代码有一处异步请求,但是我们通过运行代码发现,这是客户端发起请求后渲染上的,而不是通过SSR渲染后展现在客户端上的
useEffect(() => {dispath(fetchHomeData()) // 调用异步action
},[])
网页源码:
<html><head></head><body><div id="root"><div><ul><li><a href="/">首页</a></li><li><a href="/personal">个人中心</a></li></ul><div><h1>首页</h1><ul></ul><button>点我</button></div></div></div><script src="bundle_client.js"></script></body>
</html>
我们接下来会将redux与SSR相结合:
分析Next中怎么使用SSR:
我们会在使用到SSR渲染的页面中会暴露出一个异步函数
getServerSideProps
,那么Next判断是否暴露该方法判断服务端是否需要进行异步请求
我们给需要ssr渲染的页面添加静态方法用于获取数据,用于判断是否需要数据是否需要SSR渲染
Home.getInitialData = async (store) => {return store.dispath(fetchHomeData()) }
导出路由说明
export const routesConfig = [{path:'/',component:Home},{path:'/personal',component:Personal} ]
服务端处理
app.get('*', (req,res) => {const promises = routesConfig?.map(route => {const component = route?.componentif(route.path === req?.url && component?.getInitialData) {return component?.getInitialData(store)} else {return null}})Promise.all(promises).then(() => {const content = ReactDOMServer.renderToString(<Provider store={store}><StaticRouter location={req.url}><RoutesList/></StaticRouter></Provider>)const html = `<html><head></head><body><div id="root">${content}</div><script src="bundle_client.js"></script></body></html>`// console.log(html)res.send(html)}) })
此时我们观察网页,有以下几点发现:
页面中列表渲染的内容并没有出现
{homeData?.articles.map((article) => {return(<li key={article.key}><p>{article?.title}</p><p>{article?.content}</p></li>)}) }
控制台中出现了相应的错误信息
Warning: Did not expect server HTML to contain a <li> in <ul>.Error: Hydration failed because the initial UI does not match what was rendered on the server. 注水失败是因为初始的UI和服务器上呈现的UI不匹配
查看网页源代码(数据已经渲染且没有错误)
<html><head></head><body><div id="root"><div><ul><li><a href="/">首页</a></li><li><a href="/personal">个人中心</a></li></ul><div><h1>首页</h1><ul><li><p>文章标题1</p><p>文章内容1</p></li><li><p>文章标题2</p><p>文章内容2</p></li></ul><button>点我</button></div></div></div><script src="bundle_client.js"></script></body> </html>
这是因为客户端和服务端
store
的数据不一致,导致客户端的UI与服务端返回的UI不一致修改store的注册方式
// 工厂函数,每次使用都会创建一个新的实例
export default function createStoreInstance(preloadedState = {}) {return configureStore({reducer:{home:homeReducer,personal:personalReducer},preloadedState})
}
修改服务端中的使用:
import createStoreInstance from './store'app.get('*', (req,res) => {// 新添加const store = createStoreInstance()const promises = routesConfig?.map(route => {const component = route?.componentif(route.path === req?.url && component?.getInitialData) {return component?.getInitialData(store)} else {return null}})Promise.all(promises).then(() => {// 新添加const preloadedState = store.getState()const content = ReactDOMServer.renderToString(<Provider store={store}><StaticRouter location={req.url}><RoutesList/></StaticRouter></Provider>)const html = `<html><head></head><body><div id="root">${content}</div><script><!-- 新添加 -->window.__PRELOAD_STATE__ = ${JSON.stringify(preloadedState)}</script><script src="bundle_client.js"></script></body></html>`// console.log(html)res.send(html)})})
重要步骤:
- 异步操作结束后,store中的数据都将被更新
const preloadedState = store.getState()
获取更新后的值- 通过
<script>
将数据置入客户端中
修改客户端中的使用:
import React from "react";
import { hydrateRoot } from 'react-dom/client';
import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom"
import RoutesList from "./routes";
import createStoreInstance from "./store";// 获取到最新的store数据
const store = createStoreInstance(window?.__PRELOAD_STATE__)// 类似render方法,但是基于ssr,只是恢复原本已经存在的DOM节点
hydrateRoot(document.querySelector('#root'),(<Provider store={store}><BrowserRouter><RoutesList/></BrowserRouter></Provider>)
)
原理实践 SSR中redux的使用相关推荐
- ssr无法在win10使用_Nuxt SSR中使用WangEditor爬坑—把对象暴打出原型
记录一下Nuxt SSR中使用WangEditor的问题,这也体现JS原型在架构场景下的重要性. 前提须知: Nuxt SPA 按照文档去使用WangEditor插件是没有问题的,因为页面渲染是在前端 ...
- Systemd 技术原理实践
来源于:干货分享 | Systemd 技术原理&实践(上)和 干货分享 | Systemd 技术原理&实践(下) 一.systemd 介绍 1 systemd 的起源 关于 syste ...
- 思维方式-《金字塔原理》书中的精髓:如何利用金字塔原理,逻辑清晰地思考问题、表达观点。
<金字塔原理>书中的精髓:如何利用金字塔原理,逻辑清晰地思考问题.表达观点. 相信很多人都遇到过这样的情况,工作中,我们花了很长的时间,准备了一份工作报告,结果不仅领导不满意,连同事也觉得 ...
- 【ML】异常检测(anomaly detection)原理 + 实践 (基于sklearn)
[ML]异常检测(anomaly detection)原理 + 实践 (基于sklearn) 原理简介 实践 加载数据 可视化数据(观察规律) 训练模型 预测和展示 调整异常值为20%的情况 原理简介 ...
- 【ML】决策树(Decision tree)原理 + 实践 (基于sklearn)
[ML]决策树(Decision tree)原理 + 实践 (基于sklearn) 原理介绍 简要介绍 原理 得分函数(信息熵) 实战 数据集 数据处理 训练 预测+评估 绘制决策树 原理介绍 简要介 ...
- 【ML】KNN 原理 + 实践(基于sklearn)
[ML]KNN 原理 + 实践(基于sklearn) 原理介绍 基本原理 K的选取 特征归一化 什么是归一化?为什么要归一化? 如何归一化? 实践 数据集 加载数据 可视化数据,观察规律 训练数据 预 ...
- 【ML】Mean-Shift 原理 + 实践(基于sklearn)
[ML]Mean-Shift 原理 + 实践(基于sklearn) 原理 实践 生成数据 训练 预测+评估 原理 取数据集中的一个点为X,以此点为中心画一个半径为R的圆,圆内共有点数量假设为K. 以此 ...
- 每天一个PS技巧(原理+实践)——制作熊猫人表情包
每天一个PS技巧(原理+实践)见: 每天一个PS技巧(原理+实践)_Dezeming的博客-CSDN博客PS是由Adobe Systems开发和发行的图像处理软件.本文的特色在于快速上手和制作一些生活 ...
- 每天一个PS/PR小技巧(原理+实践)
PS小技巧 PS是由Adobe Systems开发和发行的图像处理软件.本文的特色在于快速上手和制作一些生活中会常用的功能,并且解释这些功能的具体含义. 每天一个PS技巧(原理+实践)--制作熊猫人表 ...
最新文章
- 做 AI 大咖在顶级单位之间随兴漂移,好开心!
- 核弹级漏洞log4shell席卷全球!危及苹果腾讯百度网易,修改iPhone名称就可触发...
- 并发安全Sync包的使用
- 二十六、爬取拉钩网Python职位的数据
- (算法)最长递增子序列
- 360集团或将推出数字安全免费新品
- 【ElasticSearch】Es 源码之 NetworkService 源码解读
- NYOJ 570欧拉函数求和(欧拉函数数论入门)
- C语言旅途之用for循环与break求最大素数(质数)
- 1196踩方格—递推方法!
- python爬房源信息_Python爬取链家二手房源信息
- pyqt5标签中的字设置不同字体_PyQt5 控件字体样式等设置的实现
- KDD 2017 参会报告
- 无线通信技术_Fundamentals of Wireless Communication_QA
- 一键部署天猫精灵高分电影推荐语音技能
- 【Wireshark系列一】Wireshark基本用法
- 中国菜刀能在linux上运行吗,【Web Shell】- 技术剖析中国菜刀
- 关于反函数的二阶导数
- 黑马程序员——构造器和方法
- 最强的数据扩增方法竟然是添加标点符号?