当前位置: 首页 > news >正文

【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 需要使用 forwardRefuseImperativeHandle 函数,具体见这里。

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 为例&#xff0c;使用模态框编写一个简单的消息框&#xff1a; import { useState } from "react"; import { Modal } from "react-bootstrap"; import Button from "react-bootstrap/Button"; import bootstrap/dist/css/b…...

tomcat10部署踩坑记录-公网IP和服务器系统IP搞混

1. 服务器基本条件 使用的阿里云服务器&#xff0c;镜像系统是Ubuntu16.04java version “17.0.11” 2024-04-16 LTS装的是tomcat10.1.24阿里云服务器安全组放行了&#xff1a;8080端口 服务器防火墙关闭&#xff1a; 监听情况和下图一样&#xff1a; tomcat正常启动&#xff…...

探索Sass:Web开发的强大工具

在现代Web开发中,CSS(层叠样式表)作为前端样式设计的核心技术,已经发展得非常成熟。然而,随着Web应用的复杂性不断增加,传统的CSS书写方式逐渐暴露出一些不足之处,如代码冗长、难以维护、缺乏编程功能等。为了解决这些问题,Sass(Syntactically Awesome Stylesheets)应…...

vue组件之间的通信方式有哪些

在开发过程中&#xff0c;数据传输是一个核心的知识点&#xff0c;掌握了数据传输&#xff0c;相当于掌握了80%的内容。 Vue.js 提供了多种组件间的通信方式&#xff0c;这些方式适应不同的场景和需求。下面是4种常见的通信方式&#xff1a; 1. Props & Events (父子组件通…...

111、二叉树的最小深度

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

SpringBoot3依赖管理,自动配置

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

音视频开发17 FFmpeg 音频解码- 将 aac 解码成 pcm

这一节&#xff0c;接 音视频开发12 FFmpeg 解复用详情分析&#xff0c;前面我们已经对一个 MP4文件&#xff0c;或者 FLV文件&#xff0c;或者TS文件进行了 解复用&#xff0c;解出来的 视频是H264,音频是AAC&#xff0c;那么接下来就要对H264和AAC进行处理&#xff0c;这一节…...

vue2中封装图片上传获取方法类(针对后端返回的数据不是图片链接,只是图片编号)

在Vue 2中实现商品列表中带有图片编号&#xff0c;并将返回的图片插入到商品列表中&#xff0c;可以通过以下步骤完成&#xff1a; 在Vue组件的data函数中定义商品列表和图片URL数组。 创建一个方法来获取每个商品的图片URL。 使用v-for指令在模板中遍历商品列表&#xff0c;并…...

【C++面向对象编程】(二)this指针和静态成员

文章目录 this指针和静态成员this指针静态成员 this指针和静态成员 this指针 C中类的成员变量和成员函数的存储方式有所不同&#xff1a; 成员变量&#xff1a;对象的成员变量直接作为对象的一部分存储在内存中。成员函数&#xff1a;成员函数&#xff08;非静态成员函数&am…...

最大矩形问题

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

LeetCode62不同路径

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

GNU Radio实现OFDM Radar

文章目录 前言一、GNU Radio Radar Toolbox编译及安装二、ofdm radar 原理讲解三、GNU Radio 实现 OFDM Radar1、官方提供的 grc①、grc 图②、运行结果 2、修改后的便于后续可实现探测和通信的 grc①、grc 图②、运行结果 四、资源自取 前言 本文使用 GNU Radio 搭建 OFDM Ra…...

东方博宜1760 - 整理抽屉

题目描述 期末考试即将来临&#xff0c;小T由于同时肩负了学习、竞赛、班团活动等多方面的任务&#xff0c;一直没有时间好好整理他的课桌抽屉&#xff0c;为了更好地复习&#xff0c;小T首先要把课桌抽屉里的书分类整理好。 小T的抽屉里堆着 N 本书&#xff0c;每本书的封面上…...

react快速开始(四)-之Vite 还是 (Create React App) CRA? 用Vite创建项目

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

使用python绘制核密度估计图

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

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文本编辑器的使用

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…...

【数据结构】链表与顺序表的比较

不同点&#xff1a; 顺序表和链表是两种常见的数据结构&#xff0c;他们的不同点在于存储方式和插入、删除操作、随机访问、cpu缓存利用率等方面。 一、存储方式不同: 顺序表&#xff1a; 顺序表的存储方式是顺序存储&#xff0c;在内存中申请一块连续的空间&#xff0c;通…...

dart 基本语法

//入口方法 main() 或 void main() //数据类型 原生数据类型 String int double bool null 注意&#xff1a;String 包函 ‘’ “” ‘’’ ‘’’ 三种形式复杂数据类型 list Set Map自定义数据类型 class inheritance动态数据类型 var 注&#xff1a;dart 是静态类型语言&a…...

【经验分享】嵌入式入坑经历(选段)

文章目录 你现在的工作中所用到的专业知识有哪些呢&#xff1f;为什么想转行了&#xff1f;后来为什么从事了嵌入式行业呢?你对嵌入式的兴趣是何时培养起来的?你是怎么平衡兴趣爱好和工作的关系的?平时做的事情对你现在的工作有哪些帮助?对于有志学习嵌入式开发的在校大学生…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

Docker 运行 Kafka 带 SASL 认证教程

Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明&#xff1a;server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

站群服务器的应用场景都有哪些?

站群服务器主要是为了多个网站的托管和管理所设计的&#xff0c;可以通过集中管理和高效资源的分配&#xff0c;来支持多个独立的网站同时运行&#xff0c;让每一个网站都可以分配到独立的IP地址&#xff0c;避免出现IP关联的风险&#xff0c;用户还可以通过控制面板进行管理功…...

Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storms…...