目录
[🧠 核心逻辑解析](#🧠 核心逻辑解析)
[1. 数据驱动](#1. 数据驱动)
[2. genComponent 函数的设计](#2. genComponent 函数的设计)
[3. nanoid 的作用](#3. nanoid 的作用)
[4. Redux 流程](#4. Redux 流程)
代码示例
javascript
// 导入 React 和相关 Hooks
import React, { FC, useCallback } from 'react'
// 用于生成唯一 ID 的库
import { nanoid } from 'nanoid'
// Ant Design 的 Typography 组件,用于展示标题
import { Typography } from 'antd'
// Redux 相关:用于派发 action
import { useDispatch } from 'react-redux'
// 导入所有问卷组件的配置定义
// componentConfGroup 是一个包含了所有组件分组和配置的数组
// ComponentConfType 定义了单个组件配置的 TypeScript 类型
import { componentConfGroup, ComponentConfType } from '../../../components/QuestionComponents'
// 导入 Redux action,用于向 store 中添加一个新的组件
import { addComponent } from '../../../store/componentsReducer'
// 导入 CSS Module 样式文件
import styles from './ComponentLib.module.scss'
// 解构 Ant Design 的 Title 组件,方便使用
const { Title } = Typography
/**
* 生成单个组件元素的函数
*
* 这是一个独立的函数,用于为每个组件配置项生成一个可点击的 React 元素。
* 注意:它接收一个组件配置对象 `c` 作为参数。
*/
function genComponent(c: ComponentConfType) {
// 从配置对象中解构出必要的属性
const { title, type, Component, defaultProps } = c
// 获取 Redux 的 dispatch 函数,用于触发状态更新
const dispatch = useDispatch()
// 使用 useCallback 优化点击处理函数
// 依赖项为空数组 [],意味着这个函数在整个组件生命周期中只创建一次
// 这样可以避免 genComponent 每次被调用时都创建一个新的函数引用,有助于性能优化
const handleClick = useCallback(() => {
// 当用户点击该组件时,派发一个 'addComponent' action
dispatch(
addComponent({
fe_id: nanoid(), // 使用 nanoid 生成一个唯一的前端 ID
title, // 组件标题,如 "单选题"
type, // 组件类型,如 "questionRadio"
props: defaultProps, // 组件的默认属性,如 placeholder, title 等
})
)
}, []) // 依赖数组为空,确保函数引用稳定
// 返回一个 JSX 元素,代表侧边栏中的一个组件图标
return (
<div
key={type} // React 列表 key,这里 type 是唯一的
className={styles.wrapper} // 外层容器样式,通常包含边框、内边距等
onClick={handleClick} // 绑定点击事件
>
<div className={styles.component}>
{/*
这里渲染的是组件的"预览图"。
注意:Component 是一个 React 组件函数/类,这里通过 <Component /> 的方式执行它。
通常在侧边栏中,这些组件会处于"预览模式",展示其基本形态但不可交互。
*/}
<Component />
</div>
</div>
)
}
/**
* 组件库主组件 (SideBar)
*
* 这是侧边栏的入口组件,负责组织和展示所有的组件分组。
*/
const Lib: FC = () => {
return (
<>
{/*
遍历 componentConfGroup 数组。
componentConfGroup 通常是这样结构的数据:
[
{ groupId: '1', groupName: '通用组件', components: [...] },
{ groupId: '2', groupName: '高级组件', components: [...] }
]
*/}
{componentConfGroup.map((group, index) => {
// 解构出分组 ID、分组名称和该分组下的组件列表
const { groupId, groupName, components } = group
return (
<div key={groupId}>
{/* 渲染分组标题 */}
<Title
level={3}
style={{
fontSize: '16px',
// 如果不是第一个分组 (index > 0),则添加上边距,与上一个分组拉开距离
marginTop: index > 0 ? '20px' : '0'
}}
>
{groupName}
</Title>
{/* 渲染该分组下的所有组件 */}
<div>
{/* 调用 genComponent 函数,为每个组件生成一个可点击的元素 */}
{components.map(c => genComponent(c))}
</div>
</div>
)
})}
</>
)
}
expo
🧠 核心逻辑解析
1. 数据驱动
- 数据源: 代码的核心是
componentConfGroup。这是一个配置文件,里面定义了所有可用的组件(如 Input, Radio, Checkbox)。 - 结构: 它通常是一个数组,里面包含分组信息,每个分组里又包含具体的组件配置对象。
2. genComponent 函数的设计
- 目的: 将数据(配置)转化为视图(UI)和行为(逻辑)。
- 闭包:
handleClick函数被定义在genComponent内部,它"记住"了当前循环到的组件配置c(通过解构赋值)。当你点击"单选题"时,它就知道要添加一个单选题的实例。
3. nanoid 的作用
- 每当用户点击添加组件,都会调用
nanoid()生成一个新的fe_id。 - 意义: 这保证了画布上可以存在多个相同的组件(比如两个单选题),但它们拥有不同的 ID,从而可以拥有不同的属性(比如不同的标题、不同的选项)。
4. Redux 流程
- 触发: 点击组件。
- 动作:
dispatch(addComponent(...))。 - 结果: Redux Store 中的
componentsReducer会接收到这个动作,将新的组件配置添加到当前问卷的组件列表数组中。一旦 Store 更新,订阅了该状态的"画布"组件就会重新渲染,显示出新添加的题目。