从零实现一套低代码(保姆级教程) --- 【6】在项目中使用redux状态管理

摘要

在上一篇文章中的末尾,我们也完成了Input组件的属性面板配置。现在我们的低代码项目已经小有成就了。但是后面的内容还是不少的。

如果你是第一次看到这篇文章,那么请移步到第一节:
从零实现一套低代码(保姆级教程) — 【1】初始化项目,实现左侧组件列表

在这里插入图片描述
来到本系列的第六节,我们回顾一下之前的实现内容。

1. 当我从左侧拖拽组件到画布区时,是怎么实现组件交互的 2. 当我选中画布区的组件时,右侧属性面板是怎么展示的 3. 未来我们可能通过点击层级树,选中节点要怎么做 4. 未来我们可能会在全局放置一个模型,要怎么做等等等

前两点,我们目前是通过window,来实现组件的信息传递的。那既然有很多地方用,所以我们需要有一个全局的状态管理,管理组件的信息。

所以,这时候我们就要引入redux了,用redux我们可以统一管理组件相关的信息。这样就不需要在我们的leftPart和rightPart或者mainPart中,处理组件的存储管理了。

我们开始

在这里插入图片描述

1.引入redux

OK,现在我们进行引入redux,这里我们使用的是@reduxjs/toolkit,所以在控制台输入:

 npm install --save @reduxjs/toolkit

安装完成后,在pages同级目录下,新建一个store文件夹

在这里插入图片描述
在index.ts中,我们写一下redux的初始化逻辑。

import { configureStore } from '@reduxjs/toolkit'const initialState  = { comList: [], dragCom: void 0 }const comReducer = (state: any = initialState, action: any) => {}export default configureStore({ reducer: comReducer,middleware:getDefaultMiddleware => getDefaultMiddleware({//关闭redux序列化检测serializableCheck:false})
});

在这里,我们用暂时先用两个state,一个是comList,用来表示当前画布区的组件列表。一个是nowCom,用来表示从左侧拖拽组件的type(就是从左侧组件列表拖拽的组件)。

那如果我们将组件的信息保存在redux中,当redux里面的内容发生改变时,在使用redux的地方就需要更新,所以我们封装一个自定义HOOK,作为当redux数据发生改变时,更新对应的组件。

在store中新建一个subscribe文件:

import { useState, useEffect } from 'react'
import Store from './index'function subscribeHook() {// eslint-disable-next-line react-hooks/rules-of-hooksconst [update, setUpdate] = useState<any>({})// eslint-disable-next-line react-hooks/rules-of-hooksuseEffect(() => {Store.subscribe(() => {setUpdate({...update})})}, [])
}export {subscribeHook
}

补充一句,目前我们要引入redux,就是要将我们之前的window上挂载的东西,全部干掉!

2.修改左侧组件到画布区

之前,我们从左侧组件列表,拖拽组件的时候,在onDragStart方法里,在window上挂载了一个nowCom属性。现在,我们不在window上挂载了,直接修改Store中的dragCom。

import Store from '../../../store';const onDragStart = (name: string) => {return () => {// 更新当前拖拽的节点Store.dispatch({type: 'changeNowCom', value: name});}}

那我们之前是在什么地方引用的window.nowCom呢?是在mainPart中,现在我们改为从Store中拿,同时将更新组件的subscribe方法引入。

import Store from '../../../store/index'
import { subscribeHook } from '../../../store/subscribe'export default function MainCom() {const [comList, setComList] = useState<ComJson []>([])const [dragCom, setDragCom] = useState<ComJson | null>(null)const [selectId, setSelectId] = useState<string>('')// 拿到当前拖拽的节点类型const nowCom = Store.getState().dragComsubscribeHook()const onDrop = (e: any) => {// 其他代码}else{style = {position: 'absolute',left: distance.current.endLeft + 'px',top: distance.current.endTop + 'px',zIndex:100}let comId = `comId_${Date.now()}`const comNode = {// 不从window上拿,直接从store中取comType: nowCom,style,comId}comList.push(comNode)window.renderCom = comNode;window.comList = comList;window.setComList = setComListsetSelectId(comId)}setComList([...comList])}

切记,subscribe方法是一定要记得引入,不然无法让画布区组件更新。

现在,我们从左侧拖拽组件也保持正常了。

3.修改画布区中组件拖拽

在mainPart中,我们也不需要使用自己的state去管理comList了,直接从Store中拿就行了。

  const comList = JSON.parse(JSON.stringify(Store.getState().comList))

记得之前,我们在画布区拖拽组件调整位置的时候,是通过dragCom来保存当前节点的信息。现在我们只需要记录一个comId,然后去comList中找即可。

  const [dragComId, setDragComId] = useState<string>('')const onDragStart = (com: ComJson) => {return (e: any) => {// 设置当前拖拽节点的comIdsetDragComId(com.comId);distance.current.startLeft = e.clientX;distance.current.startTop = e.clientY;}}

在拖拽结束时,我们可以去comList中找到对应的节点,然后修改它的style。最后,通过Store.dispatch去更新画布区。

  const onDrop = (e: any) => {// 只需要判断是否有dragComIdif(dragComId) {const node = comList.find((item:ComJson) => item.comId === dragComId)node.style = {...node.style,left: parseInt(node.style.left) + (e.clientX - (distance.current.startLeft || 0)) + 'px',top: parseInt(node.style.top) + (e.clientY - (distance.current.startTop || 0)) + 'px'}// 切记,拖拽完组件要记得清空这个idsetDragComId('')}else{// 其他代码}// 更新Store,从而更新画布区Store.dispatch({type: 'changeComList', value: comList})}

因为这一篇章修改的会比较多,最好可以对比着github的提交记录来看,因为上面有着改动记录。

在说右侧属性面板之前,我先画一个图来表示上面的逻辑,让读者更加清晰一点。

在这里插入图片描述

4.右侧属性面板的显示

之前我们是通过window上的renderCom,以及将setComList挂载在window上,和右侧属性面板进行通信,现在不用了。我们直接从Store中取。

首先在Store中,我们新增一个变量,用于存储选中的节点(就是右侧属性面板对应的组件ID)。

const initialState  = { comList: [], dragCom: '', selectCom: '' }const comReducer = (state: any = initialState, action: any) => {switch (action.type) {case 'changeNowCom': {return {...state, dragCom: action.value}}case 'changeComList': {return {...state, comList: action.value}}// 增加selectCom用来表示选中的节点case 'changeSelectCom': {return {...state, selectCom: action.value}}default: {return state}}
}

在mainPart中,当我拖拽组件结束时,或者点击组件的时候,就要更新选中的节点。

  const onDrop = (e: any) => {distance.current.endLeft = e.clientX;distance.current.endTop = e.clientY;let style: any;if(dragComId) {// 其他代码,setDragComId('')// 更改当前选中的节点IDStore.dispatch({type: 'changeSelectCom', value: dragComId});}else{// 其他代码setSelectId(comId)// 更改当前选中的节点IDStore.dispatch({type: 'changeSelectCom', value: comId})}Store.dispatch({type: 'changeComList', value: comList})}const selectCom = (com: ComJson) => {return () => {setSelectId(com.comId);// 更新当前选中的节点Store.dispatch({type: 'changeSelectCom', value: com.comId});}}

现在我们来到右侧属性面板,我们也不需要update这个变量了,我们从Store中拿到comList和selectCom,这样我们就知道要渲染什么类型的组件属性了。

我们修改一下getAttributePanel方法:

import Store from '../../../store/index'
import { subscribeHook } from '../../../store/subscribe'const comList = JSON.parse(JSON.stringify(Store.getState().comList))const selectCom = Store.getState().selectComconst selectNode = comList.find((item: any) => item.comId === selectCom)subscribeHook()const getAttributePanel = () => {// 不从window上拿了。直接从store中取。const comType = selectNode?.comType;const comAttributeList = attributeMap[comType] || []return <div>{comAttributeList.map((item,index) => {return <div key={index} className='attributeItem'><label className='attributeLabel'>{item.label}</label><div className='attributeItemValue'><InputComponent {...item} onChange={changeComAttribute(item.value)}/></div></div>})}</div>}

这里面有一个问题是什么呢?因为根据组件ID找到对应组件的方法,比较常用,不能每次都取comList遍历结果。所以我们后面会封装一个方法,专门用来处理,根据ID找对应节点的情况。

现在,当你拖拽组件或者点击组件的时候,右侧属性面板就可以直接显示了。不需要像之前那样,还要切换tab页签。

5.修改属性到组件渲染

现在我们就差最后一步了,就是在右侧属性面板中修改组件属性,然后映射到组件上了。

之前我们是通过window上的setComList修改组件,现在呢,我们要通过Store的dispatch方法去修改组件的属性:

  const changeComAttribute = (value: string) => {return (e: any) => {let attribute = e;if(typeof e === 'object') {attribute = e.target.value;}// 通过Store的dispatch更改组件属性selectNode[value] = attribute;Store.dispatch({type: 'changeComList', value:comList})}}

现在,你就可以更改组件属性来尝试了,组件会正常根据属性的配置进行渲染。

但是呢?有一个问题,当我给第一个组件配置好属性后,选中第二个组件。你会发现,右侧的属性面板还是之前的配置。这是为甚呢?

因为我们在实现右侧属性面板的时候,并没有做回显的功能,那属性面板应该怎么回显呢?
应该根据当前组件的属性,去回显。OK,现在我们实现一下:
首先给InputComponent组件,将当前节点传递过去

  const getAttributePanel = () => {// 其他代码// selectNode当做节点穿过去<InputComponent selectNode={selectNode} {...item} onChange={changeComAttribute(item.value)}/></div></div>})}</div>}

来到InputComponent组件里面,我们修改一下组件的默认值:

  const { onChange, type, defaultValue, options, selectNode, value } = propsconst getComponent = () => {switch (type) {case 'input': {// value为组件的默认值return <Input value={selectNode[value] || ''} style={{width:'120px'}} defaultValue={defaultValue} onChange = {onChange}/>}case 'switch': {return <Switch value={selectNode[value] || false} defaultValue={defaultValue} onChange = {onChange}/>}case 'select': {return <Select value={selectNode[value] || defaultValue} style={{width:'120px'}}  options={options} defaultValue={defaultValue} onChange={onChange}></Select>}}}

到这里,我们就实现了redux的引入,整个项目的组件,我们就使用redux进行管理了。
现在检查一下你的代码是否还有window,如果有,请删掉,如果删掉不好使,那一定是哪里漏了。

因为改动比较大,引入了redux之后,删了不少之前的代码,所以建议读者还是照着github的提交记录来看。

在这里插入图片描述

本章内容会提交在github上:
https://github.com/TeacherXin/XinBuilder2
commit: 第六节:在项目中使用redux状态管理

如果可以的话,可以给博主的GitHub点亮一颗小星星(╹▽╹)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/578700.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

防雷接地设备综合应用方案

防雷接地设备是一种用于保护建筑物、设备和人员免受雷电危害的设备。 防雷接地设备主要包括以下几种&#xff1a; 防雷针&#xff1a;防雷针是一种用于吸引雷电并将其导入地面的金属棒&#xff0c;通常安装在建筑物的最高点或其他易受雷击的位置。 防雷带&#xff1a;防雷带…

【论文笔记】BiFormer: Vision Transformer with Bi-Level Routing Attention

论文地址&#xff1a;BiFormer: Vision Transformer with Bi-Level Routing Attention 代码地址&#xff1a;https://github.com/rayleizhu/BiFormer vision transformer中Attention是极其重要的模块&#xff0c;但是它有着非常大的缺点&#xff1a;计算量太大。 BiFormer提…

Halcon颜色提取,基于MLP自动颜色提取功能

1.前言 在实际的图像处理中&#xff0c;经常会遇到彩色图像&#xff0c;使用彩色图像往往跟颜色识别有关系。但是使用RGB进行调参时又很难达到所需要的效果&#xff08;异常区域过多不好处理&#xff09;。 在Halcon中&#xff0c;halcon对颜色提取采用MLP&#xff08;多层感知…

Hive 部署

一、介绍 Apache Hive是一个分布式、容错的数据仓库系统&#xff0c;支持大规模的分析。Hive Metastore&#xff08;HMS&#xff09;提供了一个中央元数据存储库&#xff0c;可以轻松地进行分析&#xff0c;以做出明智的数据驱动决策&#xff0c;因此它是许多数据湖架构的关键组…

C/C++ 递增/递减运算符和指针

可以将递增运算符用于指针和基本变量。本书前面介绍过。将递增运算符用于指针时。将把指针的值增加其指向的数据类型占用的字节数&#xff0c;这种规则适用于对指针递增和递减。 double arr[5] {1.1, 2.1, 3.1, 4.1, 5.1}; double *ptr arr; ptr; 也可以结合使用这些运算符和…

第十部分 欧拉图与哈密顿图

欧拉图&#xff1a; 历史背景&#xff1a; 哥尼斯堡七桥问题与欧拉图 问题提出后&#xff0c;很多人对此很感兴趣&#xff0c;纷纷进行试验&#xff0c;但在相当长的时间里&#xff0c;始终未能解决。而利用普通数学知识&#xff0c;每座桥均走一次&#xff0c;那这七座桥所有的…

软件架构的演进过程

软件架构的发展经历了由单体架构、垂直架构、SOA架构到微服务架构的演进过程&#xff0c;下面我们分别了解一下这几个架构。 一, 单体架构 一个归档包&#xff08;例如war格式或者Jar格式&#xff09;包含了应用所有功能的应用程序&#xff0c;我们通常称之为单体应用。架构单…

共模电容:又一款EMC滤波神器?|深圳比创达电子(下)

一、共模电容 1、结构特性 图7 共模电容结构示意 如图7&#xff0c;共模电容是在普通叠层电容基础上&#xff0c;结合3端电容中为降低电容ESL的优化设计&#xff0c;添加了一组GND&#xff1b;同时这组GND还有一定的屏蔽作用&#xff0c;可降低电极的边缘辐射。 2、电气特性…

记一次redis内存没满发生key逐出的情况。

现象&#xff1a; 从监控上看&#xff0c;redis的内存使用率最大是80%&#xff0c;但是发生了key evicted 分析&#xff1a; 原因1、可能是阿里云监控没抓取到内存100%监控数据。 阿里控制台监控监控粒度是5秒。 内存使用率的计算方法。 used_memory_human/maxmemory 原因2、…

drf之路由

一 路由Routers 对于视图集ViewSet&#xff0c;我们除了可以自己手动指明请求方式与动作action之间的对应关系外&#xff0c;还可以使用Routers来帮助我们快速实现路由信息。 REST framework提供了两个router SimpleRouterDefaultRouter 1.1 使用方法 1&#xff09; 创建r…

自编码器的基本概念

这里写目录标题 全连接自编码器卷积自编码器正则自编码器:变分自编码器2. **VAE的改进&#xff1a;**3. **关键概念&#xff1a;**4. **目标函数&#xff1a;**5. **生成新样本&#xff1a;**6. **应用领域&#xff1a;** 全连接自编码器 自编码器是一种无监督学习模型&#x…

【c++】入门2

函数重载 函数重载&#xff1a;是函数的一种特殊情况&#xff0c;C允许在同一作用域中声明几个功能类似的同名函数&#xff0c;这 些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同&#xff0c;常用来处理实现功能类似数据类型 不同的问题。 c区分重载函数是根据参数…

搬运机器人RFID传感器CNS-RFID-01|1S的RS485(MODBUS|HS协议)通讯连接方法

搬运机器人RFID传感器CNS-RFID-01|1S支持RS485通信&#xff0c;可支持RS485&#xff08;MODBUS RTU&#xff09;协议、RS485-HS协议&#xff0c;广泛应用于物流仓储&#xff0c;立库 AGV|无人叉车|搬送机器人等领域&#xff0c;常用定位、驻车等&#xff0c;本篇重点介绍CNS-RF…

Ubuntu20.04-查看GPU的使用情况及输出详解

1. 查看GPU的使用情况 1.1 nvidia-smi # 直接在终端得到显卡的使用情况 # 不会自动刷新 nvidia-smi# 重定向到文件中 nvidia-smi > nvidia_smi_output.txt# 如果输出的内容部分是以省略号表示的&#xff0c;可以-q nvidia-smi -q 1.2 nvidia-smi -l # 会自动刷新&#x…

CleanMyMac X2024免费许可证及功能详细讲解

一些用户反映自己的CleanMyMac卸载不干净&#xff1f;你的卸载方式正确码&#xff1f;当你在Mac上安装使用CleanMyMac后&#xff0c;需要将软件卸载&#xff0c;你会使用怎样方法完成操作呢&#xff1f;小编今天主要讲解如何卸载CleanMyMac以及卸载这款软件时应该注意的事项。一…

SpringBoot 3.2.0 基于Logback定制日志框架

依赖版本 JDK 17 Spring Boot 3.2.0 工程源码&#xff1a;Gitee 日志门面和日志实现 日志门面&#xff08;如Slf4j&#xff09;就是一个标准&#xff0c;同JDBC一样来制定“规则”&#xff0c;把不同的日志系统的实现进行了具体的抽象化&#xff0c;只提供了统一的日志使用接…

re模块(正则)

【 一 】 re模块概述 在线测试工具 正则表达式在线测试 - 站长工具 随着正则表达式越来越普遍&#xff0c;Python 内置库 re 模块也支持对正则表达式使用 Python 提供了re模块可以支持正则表示表达式使用&#xff0c;re模块提供了9个常量、12个函数 使用方法&#xff1a; re…

FRP 内网穿透指南:简单上手,快速入门

最近受朋友启发&#xff0c;突然萌生了一个想法&#xff0c;那就是如何将家里闲置五六年的台式机给利用起来&#xff0c; 本来打算组装一个NAS存储服务器&#xff0c;但是硬盘实在是有点小贵&#xff0c;所以决定先买了一块799元的4T机械硬盘&#xff0c; 然后做的frp内网穿透&…

同步与互斥(三)

一、递归锁 /* 创建一个递归锁&#xff0c;返回它的句柄。 * 此函数内部会分配互斥量结构体 * 返回值: 返回句柄&#xff0c;非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );/* 释放 */ BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t…

全自动智能四向车系统|海格里斯HEGERLS四向穿梭车机器换人 科技赋能

近年来面对用户小批量、多品种、定制化产品服务需求日渐增多&#xff0c;制造行业仓储库容利用率低、分拣效率低、无法快速响应等问题更加凸显&#xff01;核心设备也由传统货架转变为智能仓储设备立体货架的存储方式&#xff0c;形成更加自动化、智能化的系统集成物流体系。其…