原理实践 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 中的规范化数据

快速上手

创建

  1. 创建目录,按照规范或自我习惯进行进行创建

    store
    ├─ index.js
    └─ slices├─ home.js└─ personal.js
    
  2. 根据不同的业务创建不同的仓库片端

    // 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
    
  3. 创建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
    
  4. 嵌套在组件较外层(前后端路由都需嵌套)

    // 服务端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判断是否暴露该方法判断服务端是否需要进行异步请求

  1. 我们给需要ssr渲染的页面添加静态方法用于获取数据,用于判断是否需要数据是否需要SSR渲染

    Home.getInitialData = async (store) => {return store.dispath(fetchHomeData())
    }
    
  2. 导出路由说明

    export const routesConfig = [{path:'/',component:Home},{path:'/personal',component:Personal}
    ]
    
  3. 服务端处理

    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不一致

  4. 修改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)})})

重要步骤:

  1. 异步操作结束后,store中的数据都将被更新
  2. const preloadedState = store.getState()获取更新后的值
  3. 通过<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的使用相关推荐

  1. ssr无法在win10使用_Nuxt SSR中使用WangEditor爬坑—把对象暴打出原型

    记录一下Nuxt SSR中使用WangEditor的问题,这也体现JS原型在架构场景下的重要性. 前提须知: Nuxt SPA 按照文档去使用WangEditor插件是没有问题的,因为页面渲染是在前端 ...

  2. Systemd 技术原理实践

    来源于:干货分享 | Systemd 技术原理&实践(上)和 干货分享 | Systemd 技术原理&实践(下) 一.systemd 介绍 1 systemd 的起源 关于 syste ...

  3. 思维方式-《金字塔原理》书中的精髓:如何利用金字塔原理,逻辑清晰地思考问题、表达观点。

    <金字塔原理>书中的精髓:如何利用金字塔原理,逻辑清晰地思考问题.表达观点. 相信很多人都遇到过这样的情况,工作中,我们花了很长的时间,准备了一份工作报告,结果不仅领导不满意,连同事也觉得 ...

  4. 【ML】异常检测(anomaly detection)原理 + 实践 (基于sklearn)

    [ML]异常检测(anomaly detection)原理 + 实践 (基于sklearn) 原理简介 实践 加载数据 可视化数据(观察规律) 训练模型 预测和展示 调整异常值为20%的情况 原理简介 ...

  5. 【ML】决策树(Decision tree)原理 + 实践 (基于sklearn)

    [ML]决策树(Decision tree)原理 + 实践 (基于sklearn) 原理介绍 简要介绍 原理 得分函数(信息熵) 实战 数据集 数据处理 训练 预测+评估 绘制决策树 原理介绍 简要介 ...

  6. 【ML】KNN 原理 + 实践(基于sklearn)

    [ML]KNN 原理 + 实践(基于sklearn) 原理介绍 基本原理 K的选取 特征归一化 什么是归一化?为什么要归一化? 如何归一化? 实践 数据集 加载数据 可视化数据,观察规律 训练数据 预 ...

  7. 【ML】Mean-Shift 原理 + 实践(基于sklearn)

    [ML]Mean-Shift 原理 + 实践(基于sklearn) 原理 实践 生成数据 训练 预测+评估 原理 取数据集中的一个点为X,以此点为中心画一个半径为R的圆,圆内共有点数量假设为K. 以此 ...

  8. 每天一个PS技巧(原理+实践)——制作熊猫人表情包

    每天一个PS技巧(原理+实践)见: 每天一个PS技巧(原理+实践)_Dezeming的博客-CSDN博客PS是由Adobe Systems开发和发行的图像处理软件.本文的特色在于快速上手和制作一些生活 ...

  9. 每天一个PS/PR小技巧(原理+实践)

    PS小技巧 PS是由Adobe Systems开发和发行的图像处理软件.本文的特色在于快速上手和制作一些生活中会常用的功能,并且解释这些功能的具体含义. 每天一个PS技巧(原理+实践)--制作熊猫人表 ...

最新文章

  1. 做 AI 大咖在顶级单位之间随兴漂移,好开心!
  2. 核弹级漏洞log4shell席卷全球!危及苹果腾讯百度网易,修改iPhone名称就可触发...
  3. 并发安全Sync包的使用
  4. 二十六、爬取拉钩网Python职位的数据
  5. (算法)最长递增子序列
  6. 360集团或将推出数字安全免费新品
  7. 【ElasticSearch】Es 源码之 NetworkService 源码解读
  8. NYOJ 570欧拉函数求和(欧拉函数数论入门)
  9. C语言旅途之用for循环与break求最大素数(质数)
  10. 1196踩方格—递推方法!
  11. python爬房源信息_Python爬取链家二手房源信息
  12. pyqt5标签中的字设置不同字体_PyQt5 控件字体样式等设置的实现
  13. KDD 2017 参会报告
  14. 无线通信技术_Fundamentals of Wireless Communication_QA
  15. 一键部署天猫精灵高分电影推荐语音技能
  16. 【Wireshark系列一】Wireshark基本用法
  17. 中国菜刀能在linux上运行吗,【Web Shell】- 技术剖析中国菜刀
  18. 关于反函数的二阶导数
  19. 黑马程序员——构造器和方法
  20. 最强的数据扩增方法竟然是添加标点符号?

热门文章

  1. 股份授权证明(DPOS)概述
  2. Android 使用Zxing
  3. OpenGL实现高斯模糊
  4. (浙江大学数据结构)PTA Complete Binary Search Tree (10 分)
  5. php 中eval 和javascript中eval有什么区别,DataBinder.Eval与Eval区别分析
  6. 小程序获取手机号及手机号解密
  7. 【Python界面化小程序】注意 | 假期火车票查询系统已完成啦,出行注意看这里哦→
  8. 写一个自己的shell
  9. 互联网养猪10年,网易们改变了什么?
  10. Kali开机启动配置选项