React封裝過哪些組件-下拉選擇器和彈窗表單

背景(S - Situation):在某活動管理系統中,前端頁面需要支持用戶選擇“要配置的當前活動”,并提供「新增」「編輯」功能,操作內容包括填寫活動名稱、ID、版本號等字段。原始實現邏輯分散、復用性差,不利于維護和功能拓展。

目標(T - Task):

封裝一個通用的 ActivitySelector 組件,支持以下功能:

  • ? 異步加載活動列表,支持 loading 狀態;

  • ? 支持新增 / 編輯活動信息,并自動更新下拉框內容;

  • ? 支持禁用某些選項、設置默認值;

  • ? 彈窗使用 antd.Modal


?? 實現重難點總結

1. 組件對外暴露彈窗控制器(Context 模式)

? 難點在于:如何在父組件中控制子組件內部的 Modal 行為

  • 采用 React 的 createContext + useContext 創建全局控制器;

  • 內部封裝了 openModal() 方法,供外部調用;

  • 父組件通過 useActivityModal() 獲取控制器,實現跨組件通信;

  • 解耦了彈窗的觸發邏輯,使組件更靈活、可擴展。

👉 適用于復雜業務流程、URL 參數觸發彈窗、全局快捷操作等場景。


2. 異步數據加載與狀態同步

  • 初始加載通過 fetchOptions 動態獲取活動列表;

  • 新增或編輯成功后自動刷新并回填選項;

  • 異步處理流程中加入 loading 狀態,保證用戶體驗;

  • 使用 Form.setFieldsValue() 動態填充表單數據。


3. 新增/編輯共用同一個 Modal + 表單

  • 通過 editItem 區分是“新增”還是“編輯”狀態;

  • 彈窗標題、確認邏輯復用,簡化了 UI;

  • 校驗規則、默認值、字段配置均可靈活拓展。


4. 選項禁用處理 + 名稱唯一性校驗

  • 支持傳入 disabledIds 數組動態禁用某些選項;

  • 在表單提交時手動校驗名稱重復,防止業務錯誤;

  • 校驗邏輯抽離出來便于維護或拓展為服務端驗證。


5. 默認版本號處理

  • 若用戶未填寫版本號,自動填充為指定 defaultVersion

  • 避免每次用戶手動輸入,提高使用體驗。


當然可以,咱來詳細解釋一下這句:


? 「對外暴露 context 控制器,支持父組件主動打開彈窗」是什么意思?


🔧 背景場景

我們在封裝一個組件(比如 <ActivitySelector />)時,通常 彈窗的打開/關閉是組件內部控制的,比如用戶點擊“新增”或“編輯”按鈕時,組件內部去 setModalVisible(true) 打開彈窗。

但有時候你會希望 在組件外部 主動打開彈窗,例如:

  • 有一個按鈕在父組件中,點擊它時希望直接打開彈窗(比如預設一個新活動);

  • 希望根據某個外部邏輯(例如 URL 參數)控制彈窗的顯示;

  • 在表單聯動或流程中,用戶完成前一步操作后觸發彈窗。


📦 如何實現?

這就需要組件對外暴露一個控制器(Controller),最常見的方式就是使用 React 的 Context + useContext + Provider


? 具體做法舉例(摘自你項目中的代碼)
// 創建一個 context(上下文對象)
const ActivityModalContext = createContext(null);// 暴露一個 Hook,讓外部能使用這個控制器
export const useActivityModal = () => useContext(ActivityModalContext);

然后在組件內部:

<ActivityModalContext.Provider value={{ openModal }}>{/* 組件內容 */}
</ActivityModalContext.Provider>

這樣父組件就可以寫成這樣:

import { useActivityModal } from './ActivitySelector';function ParentComponent() {const { openModal } = useActivityModal();return <Button onClick={() => openModal({ id: 'xxx' })}>外部打開彈窗</Button>;
}

? 總結:這是什么意思?

「對外暴露 context 控制器」就是指:

封裝組件時,借助 React 的 Context 機制,把內部的控制方法(如打開彈窗)暴露出去,讓外部組件也能調用它。


🚀 好處

好處說明
📦 解耦父組件無需知道 Modal 是怎么實現的,只要能打開它就行
🧩 靈活可以在任何地方調用 openModal,比如 URL 路由、定時器、其他組件事件等
👨?💻 易于測試與復用可以單獨測試控制器邏輯,也可以跨頁面/組件共享


? ActivitySelector.tsx 完整封裝代碼

import React, { useState, useEffect, createContext, useContext } from 'react';
import { Select, Modal, Form, Input, Button, message } from 'antd';const { Option } = Select;// --------- Context 控制器 ---------
const ActivityModalContext = createContext(null);export const useActivityModal = () => useContext(ActivityModalContext);// --------- 主組件封裝 ---------
const ActivitySelector = ({value,onChange,fetchOptions,onCreate,onEdit,disabledIds = [],defaultVersion = 'v1.0',
}) => {const [options, setOptions] = useState([]);const [loading, setLoading] = useState(false);const [modalVisible, setModalVisible] = useState(false);const [editItem, setEditItem] = useState(null);const [form] = Form.useForm();// 初始化加載useEffect(() => {loadOptions();}, []);const loadOptions = async () => {setLoading(true);const data = await fetchOptions?.();setOptions(data || []);setLoading(false);};const openModal = (item = null) => {setEditItem(item);form.setFieldsValue(item || { version: defaultVersion });setModalVisible(true);};const handleOk = async () => {try {const formData = await form.validateFields();// 校驗重復名稱(或其他字段)const isRepeat = options.some((item) => item.name === formData.name && item.id !== editItem?.id);if (isRepeat) {message.error('活動名稱重復,請重新輸入');return;}const result = editItem? await onEdit?.(editItem.id, formData): await onCreate?.(formData);await loadOptions();onChange?.(result); // 自動選中新項setModalVisible(false);} catch (err) {console.error('表單提交失敗', err);}};return (<ActivityModalContext.Provider value={{ openModal }}><div style={{ display: 'flex', gap: 8 }}><Selectvalue={value?.id}onChange={(val) => {const selected = options.find((item) => item.id === val);onChange?.(selected);}}loading={loading}style={{ flex: 1 }}placeholder="請選擇活動"allowClearshowSearchoptionFilterProp="children">{options.map((item) => (<Option key={item.id} value={item.id} disabled={disabledIds.includes(item.id)}>{item.name}({item.version})</Option>))}</Select><Button onClick={() => openModal()}>新增</Button><Button onClick={() => openModal(value)} disabled={!value}>編輯</Button></div><Modaltitle={editItem ? '編輯活動' : '新增活動'}open={modalVisible}onCancel={() => setModalVisible(false)}onOk={handleOk}destroyOnClose><Form form={form} layout="vertical" preserve={false}><Form.Itemlabel="活動名稱"name="name"rules={[{ required: true, message: '請輸入活動名稱' }]}><Input /></Form.Item><Form.Itemlabel="活動 ID"name="id"rules={[{ required: true, message: '請輸入活動 ID' }]}><Input /></Form.Item><Form.Itemlabel="版本號"name="version"rules={[{ required: true, message: '請輸入版本號' }]}><Input placeholder="如 v1.0" /></Form.Item><Form.Item label="擴展信息" name="meta"><Input.TextArea placeholder="可以是 JSON、備注等" /></Form.Item></Form></Modal></ActivityModalContext.Provider>);
};export default ActivitySelector;

🧪 外部調用方式(使用 Context 控制器)

import React, { useState, useEffect } from 'react';
import ActivitySelector, { useActivityModal } from './ActivitySelector';const ParentPage = () => {const [selected, setSelected] = useState(null);const { openModal } = useActivityModal();const fetchOptions = async () => {return [{ id: 'act001', name: '暑期促銷', version: 'v1.0', meta: '' },{ id: 'act002', name: '雙11預熱', version: 'v1.2', meta: '' },];};const handleCreate = async (data) => {// 模擬添加后返回新數據項return { id: 'act003', ...data };};const handleEdit = async (id, data) => {return { id, ...data };};return (<div><h2>活動管理</h2><ActivitySelectorvalue={selected}onChange={setSelected}fetchOptions={fetchOptions}onCreate={handleCreate}onEdit={handleEdit}disabledIds={['act002']}defaultVersion="v2.0"/><Button onClick={() => openModal()}>🔧 外部控制打開彈窗</Button></div>);
};export default ParentPage;

如你有后續需求(如:分頁、遠程搜索、多選、自定義彈窗樣式或 MUI 替換),可以繼續在這個封裝基礎上擴展,我也可以幫你一步步完成。需要我繼續用 STAR 法則 補充筆記內容嗎?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/88590.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/88590.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/88590.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

多租戶架構下的多線程處理實踐指南

在現代 SaaS 系統中&#xff0c;多租戶架構&#xff08;Multi-Tenant Architecture&#xff09;已成為主流。然而&#xff0c;隨著系統性能要求的提升和業務復雜度的增加&#xff0c;多線程成為不可避免的技術手段。但在多租戶環境下使用多線程&#xff0c;容易引發數據錯亂、租…

MyBatis插件機制揭秘:從攔截器開發到分頁插件實戰

一、攔截器體系架構解析 1.1 責任鏈模式在MyBatis中的實現 MyBatis通過動態代理技術構建攔截器鏈&#xff0c;每個插件相當于一個切面&#xff1a; // 攔截器鏈構建過程 public class InterceptorChain {private final List<Interceptor> interceptors new ArrayList<…

百度文心一言開源ERNIE-4.5深度測評報告:技術架構解讀與性能對比

目錄一、技術架構解讀1.1、ERNIE 4.5 系列模型概覽1.2、模型架構解讀1.2.1、異構MoE&#xff08;Heterogeneous MoE&#xff09;1.2.2、視覺編碼器&#xff08;Vision Encoder&#xff09;1.2.3、適配器&#xff08;Adapter&#xff09;1.2.4、多模態位置嵌入&#xff08;Multi…

Matplotlib 模塊入門

Python 中有個非常實用的可視化庫 ——Matplotlib。數據可視化是數據分析中不可或缺的環節&#xff0c;而 Matplotlib 作為 Python 的 2D 繪圖庫&#xff0c;能幫助我們生成高質量的圖表&#xff0c;讓數據更直觀、更有說服力。接下來&#xff0c;我們將從 Matplotlib 的概述、…

LeetCode 3169.無需開會的工作日:排序+一次遍歷——不需要正難則反,因為正著根本不難

【LetMeFly】3169.無需開會的工作日&#xff1a;排序一次遍歷——不需要正難則反&#xff0c;因為正著根本不難 力扣題目鏈接&#xff1a;https://leetcode.cn/problems/count-days-without-meetings/ 給你一個正整數 days&#xff0c;表示員工可工作的總天數&#xff08;從第…

VUE3 el-table 主子表 顯示

在Vue 3中&#xff0c;實現主子表&#xff08;主從表&#xff09;的顯示通常涉及到兩個組件&#xff1a;一個是主表&#xff08;Master Table&#xff09;&#xff0c;另一個是子表&#xff08;Detail Table&#xff09;。我們可以使用el-table組件來實現這一功能。這里&#x…

張量數值計算

一.前言前面我們介紹了一下pytorch還有張量的創建&#xff0c;而本章節我們就來介紹一下張量的計算&#xff0c;類型轉換以及操作&#xff0c;這個是十分重要的&#xff0c;我們的學習目標是&#xff1a;掌握張量基本運算、掌握阿達瑪積、點積運算 掌握PyTorch指定運算設備。Py…

部署項目頻繁掉線-----Java 進程在云服務器內存不足被 OOM Killer 頻繁殺死-----如何解決?

一、查詢系統日志grep -i "java" /var/log/messages執行這條命令&#xff0c;檢查系統日志里是否有 Java 進程被 OOM Killer 殺死的記錄。日志中反復出現以下內容&#xff1a;Out of memory: Killed process 3679325 (java) total-vm:2947000kB, anon-rss:406604kB..…

【保姆級教程】基于anji-plus-captcha實現行為驗證碼(滑動拼圖+點選文字),前后端完整代碼奉上!

前言 驗證碼作為Web應用的第一道安全防線&#xff0c;其重要性不言而喻。但你是否還在為以下問題煩惱&#xff1a; 傳統字符驗證碼用戶體驗差&#xff0c;識別率低&#xff1f;驗證碼安全性不足&#xff0c;輕易被爬蟲破解&#xff1f;前后端對接繁瑣&#xff0c;集成效率低&…

HTML-八股

1、DOM和BOM DOM是表示HTML或者XML文檔的標準的對象模型&#xff0c;將文檔中每個組件&#xff08;元素、屬性等&#xff09;都作為一個對象&#xff0c;使用JS來操作這個對象&#xff0c;從而動態改變頁面內容&#xff0c;結合等。 DOM是以樹型結構組織文檔內容&#xff0c;樹…

ADI的EV-21569-SOM核心板和主板轉接卡的鏈接說明

ADI提供給客戶很多DSP的核心板&#xff0c;比如EV-21569-SOM&#xff0c;EV-21593-SOM&#xff0c;EV-SC594-SOM等&#xff0c;非常多&#xff0c;但是沒有底板&#xff0c;光一個核心板怎么用呢&#xff1f;于是我就在想&#xff0c;我的21569評估板就有通用底板&#xff0c;能…

基于 Redisson 實現分布式系統下的接口限流

在高并發場景下&#xff0c;接口限流是保障系統穩定性的重要手段。常見的限流算法有漏桶算法、令牌桶算法等&#xff0c;而單機模式的限流方案在分布式集群環境下往往失效。本文將介紹如何利用 Redisson 結合 Redis 實現分布式環境下的接口限流&#xff0c;確保集群中所有節點的…

ubuntu播放rosbag包(可鼠標交互)

1 前言 眾所周知&#xff0c;ubuntu中播放bag包最主要的工具是rviz&#xff0c;然而rviz有一個無法忍受的缺陷就是不支持鼠標回滾&#xff0c;并且顯示的時間的ros時間&#xff0c;不是世界時間&#xff0c;因此在遇到相關bug時不能與對應的世界時間對應。基于以上&#xff0c…

一文理解緩存的本質:分層架構、原理對比與實戰精粹

&#x1f4d6; 推薦閱讀&#xff1a;《Yocto項目實戰教程:高效定制嵌入式Linux系統》 &#x1f3a5; 更多學習視頻請關注 B 站&#xff1a;嵌入式Jerry 一文理解緩存的本質&#xff1a;分層架構、原理對比與實戰精粹 “緩存讓系統飛起來”——但每一層緩存有何不同&#xff1f;…

【離線數倉項目】——電商域DIM層開發實戰

摘要本文主要介紹了電商域離線數倉項目中DIM層的開發實戰。首先闡述了DIM層的簡介、作用、設計特征、典型維度分類以及交易支付場景下的表示例和客戶維度表設計。接著介紹了DIM層設計規范&#xff0c;包括表結構設計規范、數據處理規范以及常見要求規范。然后詳細講解了DIM層的…

Unreal Engine 自動設置圖像

void UYtGameSettingSubsystem::RunHardwareBenchmark(int32 WorkScale, float CPUMultiplier, float GPUMultiplier) {UGameUserSettings* UserSettings UGameUserSettings::GetGameUserSettings();if (UserSettings){// 運行基準測試&#xff08;異步操作&#xff0c;可能需…

使用Spring Boot和PageHelper實現數據分頁

在Spring Boot項目中&#xff0c;利用PageHelper插件可以輕松實現數據分頁功能。以下是具體的實現步驟和代碼示例。添加依賴在項目的pom.xml文件中添加PageHelper和MyBatis的依賴。<dependency><groupId>com.github.pagehelper</groupId><artifactId>p…

【IT-Infra】從ITIL到CMDB,配置管理,資產管理,物理機與設備管理(含Infra系列說明)

【IT-Infra】從ITIL到CMDB&#xff0c;配置管理&#xff0c;資產管理&#xff0c;物理機與設備管理&#xff08;含Infra系列說明&#xff09; 文章目錄序&#xff1a;Infra系列說明1、ITIL 信息技術基礎架構庫&#xff08;起源&#xff09;2、CMDB 配置管理數據庫&#xff08;I…

vue使用printJS實現批量打印及單個打印 避免空白頁

本文介紹了使用print-js庫實現批量打印功能的實現方法。通過安裝print-js依賴后,創建一個batchPrintAction方法,該方法接收選中行數據,生成包含多個標簽頁的HTML字符串。每個標簽頁以表格形式展示6個數據字段,并設置了80mm50mm的標簽尺寸。方法使用PrintJS進行打印,配置了…

C++ 選擇排序、冒泡排序、插入排序

選擇排序&#xff1a;是一種簡單直觀的排序算法&#xff0c;每次均是選擇最小&#xff08;大&#xff09;的元素進行排序。選擇排序算法思想&#xff1a;1 在未排序序列中找到最小&#xff08;大&#xff09;元素&#xff0c;存放到排序序列的起始位置2 再從剩余未排序元素中繼…