【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('确定');
else
	console.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 不用 state

    const 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}>
            <MessageBoxBase
                title={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 (
            <MessageBoxBase
                title={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>
    );
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode】38.外观数列

外观数列 题目描述&#xff1a; 「外观数列」是一个数位字符串序列&#xff0c;由递归公式定义&#xff1a; countAndSay(1) "1"countAndSay(n) 是 countAndSay(n-1) 的行程长度编码。 行程长度编码&#xff08;RLE&#xff09;是一种字符串压缩方法&#xff0c…

STL中list的模拟实现

目录 list模拟实现 list节点 list的push_back()函数 list的迭代器操作&#xff08;非const&#xff09; list的迭代器操作&#xff08;const&#xff09; list迭代器const 非const优化 list的insert()函数 list的erase()函数 list的pop_back() push_front() pop_front(…

数据结构:希尔排序

文章目录 前言一、排序的概念及其运用二、常见排序算法的实现 1.插入排序2.希尔排序总结 前言 排序在生活中有许多实际的运用。以下是一些例子&#xff1a; 购物清单&#xff1a;当我们去超市购物时&#xff0c;通常会列出一份购物清单。将购物清单按照需要购买的顺序排序&…

【STM32F103】HC-SR04超声波测距

【STM32F103】HC-SR04超声波测距 一、HC-SR041、工作原理2、其他参数及时序图 二、代码编写思路三、HAL配置四、代码实现五、实验结果 前言 本次实验主要实现用stm32f103HC-SR04实现超声波测距&#xff0c;将测距数值通过串口上传到上位机串口助手 一、HC-SR04 1、工作原理 (…

String类型的二维数组怎么写

今天做题遇到一个问题&#xff1a;就是需要写String类型的二维数组时&#xff0c;我蒙圈了。后来查了资料发现&#xff0c;String类型的二维数组其实是由若干个一维数组构成的。 1.先初始化一个二维数组&#xff1a;List<List<String>> list new ArrayList<&g…

登录校验及全局异常处理器

登录校验 会话技术 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束.在一次会话中可以包含多次请求和响应会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话请求间共享数据会话跟踪方案 客户端…

MT8781安卓核心板_MTK联发科Helio G99核心板规格参数

MT8781安卓核心板采用先进的台积电6纳米级芯片生产工艺&#xff0c;配备高性能Arm Cortex-A76处理器和Arm Mali G57 GPU&#xff0c;加上LPDDR4X内存和UFS 2.2存储&#xff0c;在处理速度和数据访问速度上都有着出色的表现。 MT8781还支持120Hz显示器&#xff0c;无需额外的DSC…

Transformer模型学习(1)

Transformer模型&#xff0c;它自2017年被引入以来&#xff0c;已成为处理语言任务的主流技术。Transformer模型不仅在多个语言处理任务上取得了优异的成绩&#xff0c;而且还因为它的设计极大地推动了后续模型的发展&#xff0c;如今广泛应用于聊天机器人、翻译软件和文本生成…

CS的下载+内网穿透

CS的下载 纵向渗透&#xff1a;NC 瑞士军刀菜刀是一个hyyp协议 NC是TCP NC连接后没有任何回显 先受控房 nc.exe -l -p 12345 然后攻击方 nc.exe ip port 12345 扫描端口 上传和 nc.exe 同一目录下的文件 跳板机工具和NC的实际操作以及Termite联合管理 和nc是一样的…

2024年生成式AI使用趋势报告

生成式AI技术及产品发展概况 人工智能技术奇点降临&#xff0c;搜索成为大模型技术落地的“首站” 过去几十年&#xff0c;人工智能长期鲜有突破性的发展&#xff0c;直至2022年AI大模型技术奇点的出现&#xff0c;使得AI能力发生了颠覆性的变化&#xff0c;人工智能受到了前…

cdo | 常用命令

整理一下平时经常会使用的cdo命令 如何来更改netcdf数据中的变量名呢&#xff1f; 假设我现在有一个sst月平均数据,希望将里面的变量名称sst修改为sst_new netcdf oisst_monthly { dimensions:lat 180 ;lon 360 ;time UNLIMITED ; // (476 currently)nbnds 2 ; variable…

利用“记忆化搜索“解斐波那契数

一、题目描述 求第 n 个斐波那契数。 二、 利用"记忆化搜索"解斐波那契数 什么是记忆化搜索&#xff1f;记忆化搜索就是带有备忘录的递归。 我们先来看一下使用递归来解斐波那契数的这个过程&#xff0c;假设求第5个斐波那契数F(5)。 由图可见&#xff0c;要重复计…

【mysql数据库】mycat中间件

MyCat 简介 Mycat 是数据库 中间件 。 1、 数据库中间件 中间件 是一类连接软件组件和应用的计算机软件&#xff0c; 以便于软件各部件之间的沟通 。 例子 Tomcat web 中间件 。 数据库 中间件 连接 java 应用程序和数据库 2、 为什么要用 Mycat ① Java 与数据库紧耦合 …

Halcon 光度立体 缺陷检测

一、概述 halcon——缺陷检测常用方法总结&#xff08;光度立体&#xff09; - 唯有自己强大 - 博客园 (cnblogs.com) 上周去了康耐视的新品发布会&#xff0c;我真的感觉压力山大&#xff0c;因为VM可以实现现在项目中的80% 的功能&#xff0c;感觉自己的不久就要失业了。同时…

基于Python的校园预约打印网站的实现

基于Python的校园预约打印网站的实现 开发语言:Python 数据库&#xff1a;MySQL所用到的知识&#xff1a;Django框架工具&#xff1a;pycharm、Navicat、Maven 系统功能实现 注册 新用户首先要进行注册信息填写&#xff0c;填写完成以后进行登录即可使用此网站 打印社 分别有…

vue3 前端实现导出下载pdf文件

这样的数据实现导出 yourArrayBufferOrByteArray 就是后端返回数据 // 创建Blob对象const blob new Blob([new Uint8Array(res)], { type: application/pdf })// 创建一个表示该Blob的URLconst url URL.createObjectURL(blob);// 创建一个a标签用于下载const a document.cr…

使用Redis缓存实现短信登录逻辑,手机验证码缓存,用户信息缓存

引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency> 加配置 spring:redis:host: 127.0.0.1 #redis地址port: 6379 #端口password: 123456 #密码…

三十二篇:转化决策为行动:探索决策支持系统的深层价值

转化决策为行动&#xff1a;探索决策支持系统的深层价值 1. DSS的精髓&#xff1a;定义与核心功能 1.1 定义与作用 在现代商业的快速演变中&#xff0c;决策支持系统&#xff08;Decision Support Systems, DSS&#xff09;已成为企业获得竞争优势的重要工具。DSS是一种利用先…

全国产飞腾模块麒麟信安操作系统安全漏洞

1、背景介绍 目前在全国产飞腾模块上部署了麒麟信安操作系统&#xff0c;经第三方机构检测存在以下漏洞 操作系统版本为 内核版本为 openssh版本为 2、openssh CBC模式漏洞解决 首先查看ssh加密信息 nmap --script "ssh2*" 127.0.0.1 | grep -i cbc 可以通过修改/…

Elasticsearch 认证模拟题 - 5

一、题目 .在集群上有一个索引 food_ingredient&#xff0c;搜索需要满足以下要求&#xff1a; 三个字段 manufacturer&#xff0c;name&#xff0c;brand 都能匹配到文本 cake mix高亮 字段 name&#xff0c;并加标签排序&#xff0c;对字段 brand 正序&#xff0c;_score 降…