【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>);
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python自动化办公2.0 即将发布

第一节课&#xff1a;数据整理与清洗 第二节课&#xff1a;数据筛选、过滤与排序 第三节课&#xff1a;高级数据处理技巧 第四节课&#xff1a;数据可视化与实践案例 第五节课&#xff1a;统计分析与报表 第六节&#xff1a;常见的Excel报表 与下方的课程形成知识体系&…

判断自守数-第13届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第75讲。 判断自守数&#…

瑞吉外卖项目学习笔记(二)后台系统的员工管理业务开发

一、完善登录功能 1.1 问题分析 1.2 代码实现 package com.itheima.reggie.filter;//这是一个过滤器类 //登录检查过滤器import com.alibaba.fastjson.JSON; import com.itheima.reggie.common.R; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf…

docker compose完成简单项目部署

1. 项目环境 centos7 docker mysql redis ruoyi项目 ruoyi项目链接&#xff1a;https://gitee.com/y_project/RuoYi-Vue.git 2. 进行项目前后端代码打包 后端打包&#xff1a; 修改mysql连接的相关配置文件 RuoYi-Vue/ruoyi-admin/src/main/resources/application-dru…

vue实现左侧拖拽拉伸,展开收起

需求&#xff1a;1.左侧是个树形结构&#xff0c;有的文字过长展示不全&#xff0c;想通过拖拽显示全部的数据 2.展开收起 实现图中效果 <div class"catalog-drag"><svg t"1687228434888" class"icon" viewBox"0 0 1…

AWS中国峰会2024 半日游

亚马逊云科技中国峰会于2024年5月29-30日在上海举办 今年就去了半天&#xff0c;去年也是去过的&#xff0c;不过今年的活动个人感觉比去年略微凌乱了一点。 今年的峰会方向和去年一致&#xff0c;均是AI方向的各项内容&#xff08;基础架构、安全、服务、游戏、驾驶、各行各…

平衡二叉树的应用举例

AVL 是一种自平衡二叉搜索树&#xff0c;其中任何节点的左右子树的高度之差不能超过 1。 AVL树的特点&#xff1a; 1、它遵循二叉搜索树的一般属性。 2、树的每个子树都是平衡的&#xff0c;即左右子树的高度之差最多为1。 3、当插入新节点时&#xff0c;树会自我平衡。因此…

【计算机毕设】基于SpringBoot的房产销售系统设计与实现 - 源码免费(私信领取)

免费领取源码 &#xff5c; 项目完整可运行 &#xff5c; v&#xff1a;chengn7890 诚招源码校园代理&#xff01; 1. 研究目的 随着房地产市场的发展和互联网技术的进步&#xff0c;传统的房产销售模式逐渐向线上转移。设计并实现一个基于Spring Boot的房产销售系统&#xff0…

代理IP怎么检测?如何判断IP好坏?

当我们的数字足迹无处不在&#xff0c;隐私保护显得愈发重要。而代理IP就像是我们的隐身斗篷&#xff0c;让我们在各项网络业务中更加顺畅。 我们常常看到别人购买了代理IP服务后&#xff0c;用在线检测网站检查IP&#xff0c;相当于一个”售前检验““售后质检”的作用。但是…

[ROS 系列学习教程] 建模与仿真 - Xacro 语法

ROS 系列学习教程(总目录) 本文目录 一、属性与属性块二、数学表达式三、宏3.1 宏的基本使用3.2 属性块做为宏的入参3.3 任意数量元素做为宏的入参3.4 指定多个块元素的处理顺序3.5 宏嵌套3.6 默认参数3.7 局部属性 四、Rospack 命令五、包含其他 xacro 文件六、条件语句七、YA…

html+CSS部分基础运用9

项目1 参会注册表 1.设计参会注册表页面&#xff0c;效果如图9-1所示。 图9-1 参会注册表页面 项目2 设计《大学生暑期社会实践调查问卷》 1.设计“大学生暑期社会实践调查问卷”页面&#xff0c;如图9-2所示。 图9-2 大学生暑期社会调查表页面 2&#xff0e;调查表前导语的…

【C++】C++11新特性:列表初始化、声明、新容器、右值引用、万能引用和完美转发

目录 一、列表初始化 1.1 { } 初始化 1.2 std::initializer_list 二、声明 2.1 auto 2.2 decltype 2.3 nullptr 三、新容器 四、右值引用和移动语义 4.1 左值和左值引用 4.2 右值和右值引用 4.3 左值引用与右值引用比较 4.4 右值引用使用场景和意义&#xff1a;移…

Spring Boot 项目中使用 JSP

文章目录 Spring Boot 项目中使用 JSP项目结构引入依赖包编写页面和后台运行方式一&#xff1a;Maven 命令运行方式二&#xff1a;在 IDEA 中运行方式三&#xff1a;打 war 包部署运行 Spring Boot 项目中使用 JSP 在 Spring Boot 项目中不是不可以使用 JSP 。想在 Spring Boo…

fmql之CAN调试

刚刚把zynq的CAN调成功。那么现在就要把程序移植到fmql了。 老规矩&#xff0c;Procise导入vivado的.bd和.xci文件。 Procise下create block也可以&#xff0c;但是不能自动约束引脚&#xff0c;只能手动写代码。 PeripheralTest CanExample中用到了CAN0和CAN1&#xff1a;…

msvcp140.dll是什么东西?如何修复电脑提示msvcp140.dll丢失的多种方法

文件名为 msvcp140.dll&#xff0c;这是一个动态链接库&#xff08;DLL&#xff09;文件&#xff0c;属于Microsoft Visual C 2015 Redistributable的一部分。全称为 "Microsoft C Runtime Library" 或 "Microsoft C Runtime Library"&#xff0c;表明该文…

如何使用 Connector API 将数据提取到 Elasticsearch Serverless 中

作者&#xff1a;来自 Elastic Jedr Blaszyk Elasticsearch 支持一系列摄取方法。 其中之一是 Elastic Connectors&#xff0c;它将 SQL 数据库或 SharePoint Online 等外部数据源与 Elasticsearch 索引同步。 连接器对于在现有数据之上构建强大的搜索体验特别有用。 例如&…

ESP32入门:1、VSCode+PlatformIO环境搭建(离线快速安装)

文章目录 背景安装vscode安装配置中文 安装Platform IO安装PIO 新建ESP32工程参考 背景 对于刚接触单片机的同学&#xff0c;使用vscodeplatformIO来学习ESP32是最方便快捷的&#xff0c;比IDF框架简单&#xff0c;且比arduino文件管理性能更好。但是platformIO安装较为麻烦&a…

uniapp 添加字体ttf

效果图如下 一、逻辑概述 在uniapp中使用字体&#xff0c;一共分成两种情况&#xff0c;一种是普通vue页面&#xff0c;一种是nvue页面引入字体。。 1.vue页面引入字体需要如下步骤 1. 先选择下载一种字体&#xff1a;字体格式一般为 ttf后缀名 黄凯桦律师手写体免费下载和在线…

代码随想录算法训练营第三十二 | ● 122.买卖股票的最佳时机II ● 55. 跳跃游戏 ● 45.跳跃游戏II

122.买卖股票的最佳时机II 讲解链接&#xff1a;https://programmercarl.com/1005.K%E6%AC%A1%E5%8F%96%E5%8F%8D%E5%90%8E%E6%9C%80%E5%A4%A7%E5%8C%96%E7%9A%84%E6%95%B0%E7%BB%84%E5%92%8C.html 简单思路&#xff1a;逐个计算连续两天的股票差值&#xff0c;sum初始为零&…

期末速成 ——计算机组成原理(2)数值的表示与运算

目录 一、定点数的表示 &#xff08;一&#xff09;无符号数和有符号数的表示 &#xff08;二&#xff09;机器数的定点表示 &#xff08;三&#xff09;原码、补码、反码、移码 (1)原码表示法 二、浮点数的表示 三、溢出判断 (一)采用一位符号位 (二)采用双符号位 四…