从零实现一套低代码(保姆级教程) --- 【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-hooks
  const [update, setUpdate] = useState<any>({})

  // eslint-disable-next-line react-hooks/rules-of-hooks
  useEffect(() => {
    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().dragCom
  subscribeHook()

  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 = setComList
      setSelectId(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) => {
      // 设置当前拖拽节点的comId
      setDragComId(com.comId);
      distance.current.startLeft = e.clientX;
      distance.current.startTop = e.clientY;
    }
  }

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

  const onDrop = (e: any) => {
    // 只需要判断是否有dragComId
    if(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'
      }
      // 切记,拖拽完组件要记得清空这个id
      setDragComId('')
    }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('')
      // 更改当前选中的节点ID
      Store.dispatch({type: 'changeSelectCom', value: dragComId});
    }else{
	  // 其他代码
      setSelectId(comId)
      // 更改当前选中的节点ID
      Store.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().selectCom
  const 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 } = props

  const 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点亮一颗小星星(╹▽╹)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

抖店只能做和营业执照对照的产品吗?开店基础教程,新手可收藏!

我是王路飞。 抖店的营业执照有多重要呢&#xff1f;关系到你店铺的类型、类目和产品。 尤其是适合新手做的个体店&#xff0c;不涉及对公账户&#xff0c;货款可以直接提现到你的私人银行卡里&#xff0c;保证金也只有企业店铺的一半。 &#xff08;只需要身份证就能开通的…

Python入门之数据结构篇

文章目录 准备工作一、数组1.1 简单使用1.2 数组函数1.3 数组方法1.4 列表推导1.5 数组切片 二、元组&#xff08;tup&#xff09;2.1 简单使用2.2 元组函数 三、字典&#xff08;Dictionary&#xff09;3.1 简单使用3.2 字典函数&#xff1a;关于Python技术储备一、Python所有…

Vue.js学习笔记(1)——Visual Studio Code搭建Vue.js框架

1 开通高德地图API服务 1、进入高德地图API官网&#xff08;https://lbs.amap.com/&#xff09;&#xff1a; 2、注册登录。 3、进入控制台。 4、点击“应用管理”&#xff0c;点击“我的应用”&#xff0c;创建新应用。 5、添加Key&#xff0c;服务平台选择“Web端&#xff…

MMDetection中的数据处理

CustomDataset 代码路径&#xff1a;mmdet/datasets/custom.py mmdet中的CustomDataset继承自torch的Dataset&#xff0c;因此&#xff0c;对应的需要实现3个虚函数&#xff0c;接下来我们首先看看这3个重要函数的实现。 构造函数 CLASSES None # static变量def __init__…

关于勒索软件盛行,LockBit网络攻击者气焰嚣张的动态情报

一、基本内容 在过去一周内&#xff0c;LockBit团队颇为高调&#xff0c;他们对关键组织、政府和企业发动了一系列的攻击。该勒索软件团队发动的攻击数量远远超过其他所有的勒索团队&#xff0c;CSEM事件为近期勒索事件画上了句号 二、相关发声情况 2023年8月30日&#xff0…

数据库原理知识点清晰总结

数据库基础知识 数据库系统结构 数据库管理系统 数据库设计 关系数据库 关系数据库标准语言SQL 关系数据库规范化理论 数据库保护技术 视图 存储过程 触发器 数据的锁定

[uniapp] 文件查找失败:‘./iconfont.woff?t=1703574566334‘

uniapp 报错 文件查找失败&#xff1a;‘./iconfont.woff?t1703574566334’ uniapp引入图标的时候如果没有处理,就会报这个错误,这个错误是因为字体图标用的相对路径,uniapp不允许, 所以要解决这个错误,只需要我们去到iconfont.css文件内将相对路径改为绝对路径就好 改成 …

【模型部署入门 一】:Pytorch图像分类模型转换ImageNet1000类预训练模型转ONNX

入门学习主要是跟随同济子豪兄&#xff0c;很感谢该博主&#xff0c;本博客代码主体是子豪兄的&#xff0c;我只是总结加工整理记录。 子豪兄对应学习视频链接地址为&#xff1a;ImageNet1000类预训练模型转ONNX 我自己总结的教程中代码使用任何python编译环境都可以直接打开…

Linux系统--账号和权限管理

目录 一、Linux安全模型 二、用户账号和组账号 2.1 用户帐号类型 2.2 组账号 2.3 用户和组的关系 三、两个重要文件夹 3.1 用户账号文件/etc/passwd 3.2 /etc/shadow 四、用户命令 4.1 useradd命令——添加用户 4.2 passwd命令——密码管理 4.3 usermod命令—…

北科智慧智能手提箱旗舰版惊艳亮相2023(香港)智能科技展

2023年12月8日&#xff0c;北科智慧团队在粤港澳大湾区创新发展峰会暨侨交会2023&#xff08;香港&#xff09;智能科技展上举办新品发布&#xff0c;推出三款自主研发科技产品&#xff1a;智能手提箱高级版和旗舰版&#xff0c;以及智能呈批夹。新品一经推出&#xff0c;便受到…

PMP®项目管理,2024年1月4日开课啦~想了解的提前查看!

PMP项目管理认证 1&#x1f237;4日开课~ 想报名的提前预约啦 &#x1f447;&#x1f447;&#x1f447; &#x1f446;&#xff08;以上是PMP课程内容&#xff09; 课程介绍 PMP&#xff08;Project Management Professional&#xff09;是由美国项目管理协会&#xff08;…

Kubernetes的理论基础

k8s:kubernetes:8个字母省略&#xff0c;就是k8s。 自动部署&#xff0c;自动扩展和管理容器化部署的应用程序的一个开源系统。k8s是负责自动化运维管理多个容器化程序的集群&#xff0c;是一个功能强大的容器编排工具。分布式和集群化的方式进行容器管理。 1.15 1.18 1.20 1…

vue 基础学习 一

1. vue 使用快速入门三步走 (1) 新建 HTML 页面&#xff0c;引入 Vue.js文件 1 2 3 4 5 6 7 <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>Vue.js 入门示例</title> <script src"https://cdn.j…

Python如何实现邮件的发送

python笔记- 发送邮件 依赖&#xff1a; Python代码实现发送邮件&#xff0c;使用的模块是smtplib、MIMEText&#xff0c;实现代码之前需要导入包&#xff1a; import smtplib from email.mime.text import MIMEText 使用163邮件发送邮件&#xff0c;具体代码实现如下&#x…

计算机毕业设计选题推荐,ssm诗词打卡微信小程序 44669(赠送源码数据库 )上万套实战教程手把手教学JAVA、PHP,node.js,C++、python、数据可视化等

诗词打卡微信小程序 摘要 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;诗词打卡微信小程序被用户普遍使用&…

Cron介绍,以及常见的cron表达式

目录 一.cron介绍 1.什么是Cron&#xff1f; 2.Cron语法 时间字段的取值范围如下&#xff1a; 时间字段支持以下特殊字符&#xff1a; 下面是一些示例&#xff1a; 3.虚拟机安装cron(centos7展示) 二.常见的cron表达式 一.cron介绍 1.什么是Cron&#xff1f; Cron是一个…

视频物体对象追踪AI技术模型——Tracking Any Object Amodally

项目地址&#xff1a;https://tao-amodal.github.io 论文&#xff1a;https://arxiv.org/abs/2312.12433 GitHub&#xff1a;GitHub - WesleyHsieh0806/TAO-Amodal: Official Code for Tracking Any Object Amodally AIGC专区&#xff1a;aigc 更多消息&#xff1a;AI人工智能行…

Linux之vim编辑器

目录 vim编辑器 vim编辑器指令 命令模式指令 光标相关 移动光标相关 文本操作 底行模式指令 插入模式 vim配置 vimforcpp 面试官&#xff1a;小伙子&#xff0c;你是用什么环境编写代码的&#xff1f; 小明&#xff1a;vs2019 面试官&#xff1a;小伙子&#xff0c…

亚马逊云科技 re:Invent 2023 产品体验:亚马逊云科技产品应用实践 王炸产品 Amazon Q,你的 AI 助手

意料之中 2023年9月25日&#xff0c;亚马逊宣布与 Anthropic 正式展开战略合作&#xff0c;结合双方在更安全的生成式 AI 领域的先进技术和专业知识&#xff0c;加速 Anthropic 未来基础模型的开发&#xff0c;并将其广泛提供给亚马逊云科技的客户使用。 亚马逊云科技开发者社…