【React】封装一个好用方便的消息框(Hooks Bootstrap 实践)
引言
以 Bootstrap 为例,使用模态框编写一个简单的消息框:
import { useState } from "react";
import { Modal } from "react-bootstrap";
import Button from "react-bootstrap/Button";
import 'bootstrap/dist/css/bootstrap.min.css';function App() {let [show, setShow] = useState(false);const handleConfirm = () => {setShow(false);console.log("confirm");};const handleCancel = () => {setShow(false);console.log("cancel");};return (<div><Button variant="primary" onClick={() => setShow(true)}>弹窗</Button><Modal show={show}><Modal.Header><Modal.Title>我是标题</Modal.Title></Modal.Header><Modal.Body>Hello World</Modal.Body><Modal.Footer><Button variant="primary" onClick={handleConfirm}>确定</Button><Button variant="secondary" onClick={handleCancel}>取消</Button></Modal.Footer></Modal></div>);
}export default App;
整段代码十分复杂。
Bootstrap 的模态框使用 show
属性决定是否显示,因此我们不得不创建一个 state 来保存是否展示模态框。然后还得自己手动在按钮的点击事件里控制模态框的展示。
如果你编写过传统桌面软件,弹一个消息框应该是很简单的事情,就像
if (MessageBox.show('我是标题', 'HelloWorld', MessageBox.YesNo) == MessageBox.Yes)console.log('确定');
elseconsole.log('取消');
一样。
那么下面我们就朝着这个方向,尝试将上面的 React 代码简化。
0. 简单封装
首先从 HTML 代码开始简化。先封装成一个简单的受控组件:
import React, { useMemo } from "react";
import { useState, createContext, useRef } from "react";
import { Button, Modal } from "react-bootstrap";/*** 类 Windows 消息框组件。* @param {object} props * @param {string} props.title 消息框标题* @param {string} props.message 消息框内容* @param {string} [props.type="ok"] 消息框类型* @param {boolean} [props.showModal=false] 是否显示消息框* @param {function} [props.onResult] 消息框结果回调* @returns {JSX.Element}*/
function MessageBox(props) {let title = props.title;let message = props.message;let type = props.type || 'ok';let showModal = props.showModal || false;let onResult = props.onResult || (() => {});let buttons = null;// 处理不同按钮const handleResult = (result) => {onResult(result);};if (type === 'ok') {buttons = (<Button variant="primary" onClick={ () => handleResult('ok') }>确定</Button>);}else if (type === 'yesno') {buttons = (<><Button variant="secondary" onClick={ () => handleResult('confirm') }>取消</Button><Button variant="primary" onClick={ () => handleResult('cancel') }>确定</Button></>)}return (<div><Modal show={showModal}><Modal.Header><Modal.Title>{title}</Modal.Title></Modal.Header><Modal.Body>{message}</Modal.Body><Modal.Footer>{buttons}</Modal.Footer></Modal></div>);
}export default MessageBox;
测试:
function App() {const handleResult = (result) => {console.log(result);};return (<div><MessageBox showModal={true} title="我是标题" message="Hello World" type="ok" onResult={handleResult} /></div>);
}
HTML 代码部分简化完成。这下代码短了不少。
现在如果想要正常使用消息框,还需要自己定义 showModal
状态并绑定 onResult
事件控制消息框的显示隐藏。下面我们来简化 JS 调用部分。
1. useContext
首先可以考虑全局都只放一份模态框的代码到某个位置,然后要用的时候都修改这一个模态框即可。这样就不用每次都写一个 <MessageBox ... />
了。
为了能在任意地方都访问到模态框,可以考虑用 Context 进行跨级通信。
把“修改模态框内容 + 处理隐藏”这部分封装成一个函数 show(),然后通过 Context 暴露出去。
import { useState, createContext, useRef, useContext } from "react";
import MessageBoxBase from "./MessageBox";const MessageBoxContext = createContext(null);function MessageBoxProvider(props) {let [showModal, setShowModal] = useState(false);let [title, setTitle] = useState('');let [message, setMessage] = useState('');let [type, setType] = useState(null);let resolveRef = useRef(null); // 因为与 UI 无关,用 ref 不用 stateconst handleResult = (result) => {resolveRef.current(result);setShowModal(false);};const show = (title, message, type) => {setTitle(title);setMessage(message);setType(type);setShowModal(true);return new Promise((resolve, reject) => {resolveRef.current = resolve;});};return (<MessageBoxContext.Provider value={show}><MessageBoxBasetitle={title}message={message}type={type}showModal={showModal}onResult={handleResult}/>{props.children}</MessageBoxContext.Provider>);
}export { MessageBoxProvider, MessageBoxContext };
使用:
index.js
root.render(<React.StrictMode><MessageBoxProvider><App /></MessageBoxProvider></React.StrictMode>
);
App.js
function App() {let msgBox = useContext(MessageBoxContext);const handleClick = async () => {let result = await msgBox('我是标题', 'Hello World', 'yesno');console.log(result);if (result === 'yes') {alert('yes');} else if (result === 'no') {alert('no');}};return (<div><Button variant="primary" onClick={handleClick}>弹窗1</Button></div>);
}
为了方便使用,可以在 useContext
之上再套一层:
/** * 以 Context 方式使用 MessageBox。* @return {(title: string, message: string, type: string) => Promise<string>}*/
function useMessageBox() {return useContext(MessageBoxContext);
}
这样封装使用起来是最简单的,只需要 useMessageBox 然后直接调函数即可显示消息框。
但是缺点显而易见,只能同时弹一个消息框,因为所有的消息框都要共享一个模态框。
2. Hook
为了解决上面只能同时弹一个框的问题,我们可以考虑取消全局只有一个对话框的策略,改成每个要用的组件都单独一个对话框,这样就不会出现冲突的问题了。
即将模态框组件和状态以及处理函数都封装到一个 Hook 里,每次调用这个 Hook 都返回一个组件变量和 show 函数,调用方只需要把返回的组件变量渲染出来,然后调用 show 即可。
import React, { useMemo } from "react";
import { useState, createContext, useRef } from "react";
import MessageBoxBase from "./MessageBox";/*** 以 Hook 方式使用消息框。* @returns {[MessageBox, show]} [MessageBox, show]* @example* const [MessageBox, show] = useMessageBox(); * return (* <MessageBox />* <button onClick={() => show('title', 'message', 'ok')} >show</button>* );*/
function useMessageBox() {let [title, setTitle] = useState('');let [message, setMessage] = useState('');let [type, setType] = useState(null);let [showDialog, setShowDialog] = useState(false);let resolveRef = useRef(null);const handleResult = (result) => {resolveRef.current(result);setShowDialog(false);};const MessageBox = useMemo(() => { // 也可以不用 useMemo 直接赋值 JSX 代码return (<MessageBoxBasetitle={title}message={message}type={type}showModal={showDialog}onResult={handleResult}/>);}, [title, message, type, showDialog]);const show = (title, message, type) => {setTitle(title);setMessage(message);setType(type);setShowDialog(true);return new Promise((resolve, reject) => {resolveRef.current = resolve;});};return [MessageBox, show];
}export default useMessageBox;
App.js
function App() {const [MessageBox, show] = useMessageBox();return (<div>{MessageBox}<button onClick={ () => show('title', 'message', 'ok') }>HookShow1</button><button onClick={ () => show('title', 'message', 'yesno') }>HookShow2</button></div>);
}
3. forwardRef + useImperativeHandle
上面我们都是封装成 show() 函数的形式。对于简单的消息框,这种调用方式非常好用。但是如果想要显示复杂的内容(例如 HTML 标签)就有些麻烦了。
这种情况可以考虑不封装 HTML 代码,HTML 代码让调用者手动编写,我们只封装控制部分的 JS 代码,即 showModal
状态和回调函数。
如果是类组件,可以直接添加一个普通的成员方法 show(),然后通过 ref 调用这个方法。但是现在我们用的是函数式组件,函数式组件想要使用 ref 需要使用 forwardRef
和 useImperativeHandle
函数,具体见这里。
import { useImperativeHandle, useRef, useState } from "react";
import MessageBox from "./MessageBox";
import { forwardRef } from "react";function MessageBoxRef(props, ref) {let [showModal, setShowModal] = useState(false);let resolveRef = useRef(null);function handleResult(result) {setShowModal(false);resolveRef.current(result);}// ref 引用的对象将会是第二个参数(回调函数)的返回值useImperativeHandle(ref, () => ({show() {setShowModal(true);return new Promise((resolve, reject) => {resolveRef.current = resolve;});}}), []); // 第三个参数为依赖,类似于 useEffect()return <MessageBox {...props} showModal={showModal} onResult={handleResult} />;
}export default forwardRef(MessageBoxRef);
使用的时候只需要创建一个 ref,然后 ref.current.show() 即可。
App.js
function App() {const messageBoxRef = useRef();return (<div><MessageBoxRef ref={messageBoxRef} title="标题" message="内容" /><button onClick={ () => messageBoxRef.current.show() }>RefShow</button></div>);
}
相关文章:

【React】封装一个好用方便的消息框(Hooks Bootstrap 实践)
引言 以 Bootstrap 为例,使用模态框编写一个简单的消息框: import { useState } from "react"; import { Modal } from "react-bootstrap"; import Button from "react-bootstrap/Button"; import bootstrap/dist/css/b…...

tomcat10部署踩坑记录-公网IP和服务器系统IP搞混
1. 服务器基本条件 使用的阿里云服务器,镜像系统是Ubuntu16.04java version “17.0.11” 2024-04-16 LTS装的是tomcat10.1.24阿里云服务器安全组放行了:8080端口 服务器防火墙关闭: 监听情况和下图一样: tomcat正常启动ÿ…...
探索Sass:Web开发的强大工具
在现代Web开发中,CSS(层叠样式表)作为前端样式设计的核心技术,已经发展得非常成熟。然而,随着Web应用的复杂性不断增加,传统的CSS书写方式逐渐暴露出一些不足之处,如代码冗长、难以维护、缺乏编程功能等。为了解决这些问题,Sass(Syntactically Awesome Stylesheets)应…...
vue组件之间的通信方式有哪些
在开发过程中,数据传输是一个核心的知识点,掌握了数据传输,相当于掌握了80%的内容。 Vue.js 提供了多种组件间的通信方式,这些方式适应不同的场景和需求。下面是4种常见的通信方式: 1. Props & Events (父子组件通…...

111、二叉树的最小深度
给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 题解:找出最小深度也就是找出根节点相对所有叶子结点的最小高度,在这也表明了根节点的高度是变化的,相对不同的叶子结点有不同的高度。…...

SpringBoot3依赖管理,自动配置
文章目录 1. 项目新建2. 相关pom依赖3. 依赖管理机制导入 starter 所有相关依赖都会导入进来为什么版本号都不用写?如何自定义版本号第三方的jar包 4. 自动配置机制5. 核心注解 1. 项目新建 直接建Maven项目通过官方提供的Spring Initializr项目创建 2. 相关pom依…...

音视频开发17 FFmpeg 音频解码- 将 aac 解码成 pcm
这一节,接 音视频开发12 FFmpeg 解复用详情分析,前面我们已经对一个 MP4文件,或者 FLV文件,或者TS文件进行了 解复用,解出来的 视频是H264,音频是AAC,那么接下来就要对H264和AAC进行处理,这一节…...
vue2中封装图片上传获取方法类(针对后端返回的数据不是图片链接,只是图片编号)
在Vue 2中实现商品列表中带有图片编号,并将返回的图片插入到商品列表中,可以通过以下步骤完成: 在Vue组件的data函数中定义商品列表和图片URL数组。 创建一个方法来获取每个商品的图片URL。 使用v-for指令在模板中遍历商品列表,并…...
【C++面向对象编程】(二)this指针和静态成员
文章目录 this指针和静态成员this指针静态成员 this指针和静态成员 this指针 C中类的成员变量和成员函数的存储方式有所不同: 成员变量:对象的成员变量直接作为对象的一部分存储在内存中。成员函数:成员函数(非静态成员函数&am…...

最大矩形问题
柱状图中最大的矩形 题目 分析 矩形的面积等于宽乘以高,因此只要能确定每个矩形的宽和高,就能计算它的面积。如果直方图中一个矩形从下标为 i 的柱子开始,到下标为 j 的柱子结束,那么这两根柱子之间的矩形(含两端的柱…...

LeetCode62不同路径
题目描述 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径? …...

GNU Radio实现OFDM Radar
文章目录 前言一、GNU Radio Radar Toolbox编译及安装二、ofdm radar 原理讲解三、GNU Radio 实现 OFDM Radar1、官方提供的 grc①、grc 图②、运行结果 2、修改后的便于后续可实现探测和通信的 grc①、grc 图②、运行结果 四、资源自取 前言 本文使用 GNU Radio 搭建 OFDM Ra…...
东方博宜1760 - 整理抽屉
题目描述 期末考试即将来临,小T由于同时肩负了学习、竞赛、班团活动等多方面的任务,一直没有时间好好整理他的课桌抽屉,为了更好地复习,小T首先要把课桌抽屉里的书分类整理好。 小T的抽屉里堆着 N 本书,每本书的封面上…...

react快速开始(四)-之Vite 还是 (Create React App) CRA? 用Vite创建项目
文章目录 react快速开始(四)-之Vite 还是 (Create React App) CRA? 用Vite创建项目背景Vite 和 (Create React App) CRAVite?Vite 是否支持 TypeScript? 用Vite创建react项目参考 react快速开始(四)-之Vite 还是 (Create React App) CRA? 用Vite创建项…...

使用python绘制核密度估计图
使用python绘制核密度估计图 核密度估计图介绍效果代码 核密度估计图介绍 核密度估计(Kernel Density Estimation,KDE)是一种用于估计数据概率密度函数的非参数方法。与直方图不同,KDE 可以生成平滑的密度曲线,更好地…...

5. MySQL 运算符和函数
文章目录 【 1. 算术运算符 】【 2. 逻辑运算符 】2.1 逻辑非 (NOT 或者 !)2.2 逻辑与运算符 (AND 或者 &&)2.3 逻辑或 (OR 或者 ||)2.4 异或运算 (XOR) 【 3. 比较运算符 】3.1 等于 3.2 安全等于运算符 <>3.3 不等于运算符 (<> 或者 !)3.4 小于等于运算符…...
Linux学习之vi文本编辑器的使用
天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…...

【数据结构】链表与顺序表的比较
不同点: 顺序表和链表是两种常见的数据结构,他们的不同点在于存储方式和插入、删除操作、随机访问、cpu缓存利用率等方面。 一、存储方式不同: 顺序表: 顺序表的存储方式是顺序存储,在内存中申请一块连续的空间,通…...
dart 基本语法
//入口方法 main() 或 void main() //数据类型 原生数据类型 String int double bool null 注意:String 包函 ‘’ “” ‘’’ ‘’’ 三种形式复杂数据类型 list Set Map自定义数据类型 class inheritance动态数据类型 var 注:dart 是静态类型语言&a…...
【经验分享】嵌入式入坑经历(选段)
文章目录 你现在的工作中所用到的专业知识有哪些呢?为什么想转行了?后来为什么从事了嵌入式行业呢?你对嵌入式的兴趣是何时培养起来的?你是怎么平衡兴趣爱好和工作的关系的?平时做的事情对你现在的工作有哪些帮助?对于有志学习嵌入式开发的在校大学生…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...
【WebSocket】SpringBoot项目中使用WebSocket
1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖,添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...

【阅读笔记】MemOS: 大语言模型内存增强生成操作系统
核心速览 研究背景 研究问题:这篇文章要解决的问题是当前大型语言模型(LLMs)在处理内存方面的局限性。LLMs虽然在语言感知和生成方面表现出色,但缺乏统一的、结构化的内存架构。现有的方法如检索增强生成(RA…...
信息系统分析与设计复习
2024试卷 单选题(20) 1、在一个聊天系统(类似ChatGPT)中,属于控制类的是()。 A. 话语者类 B.聊天文字输入界面类 C. 聊天主题辨别类 D. 聊天历史类 解析 B-C-E备选架构中分析类分为边界类、控制类和实体类。 边界…...