React中使用对话框并不容易,主要因为:

  1. 对话框需要在父组件中声明,才能在子组件中控制其是否显示

  2. 给对话框传递参数只能由props传入,这意味着所有状态管理都必须在更高阶的组件中。而实际上这个对话框的参数只在子组件中才会维护。这时就需要我们使用自定义事件将参数传回

这些问题的本质就是:如何用一个统一的方式去管理对话框,从而让对话框相关的业务逻辑更加模块化,以及和其他业务逻辑进行解耦。

下面的方式只是经验总结,并不是唯一或者最佳实现:

思路:使用全局状态管理所有对话框


对话框本质上是独立于其他界面的一个窗口,用于完成一个独立的功能。

所以,定义一个对话框,定位等价于定义一个具有唯一URL路径的页面。只是前者由弹出层实现,后者是页面的切换。

对话框UI弹出过程和页面URL的切换非常类似,那么我们就可以给每一个对话框定义一个全局唯一的ID,然后通过这个ID去显示或者隐藏一个对话框,并且给它传递参数。

尝试设计一个API去做对话框的全局管理


假设我们实现的对话框为NiceModal,那么我们的目标是如下去使用:

const UserInfoModal = NiceModal.create('user-info-modal',RealUserInfoModal
)// 创建一个useNiceModal 这样的hook去获取某个id的对话框的操作对象
const modal = useNiceModal('user-info-modal')// 通过 modal.show 显示一个对话框,并能够给它传递参数
modal.show(args)
modal.hide()

可以看到,如果有这样的API,那么无论在哪个层级的组件,只要知道某个Modal的ID,那么就都可以统一使用这些对话框,而不再需要考虑该在哪个层级的组件去定义了。

实现:创建NiceModal组件和相关API


创建一个处理所有对话框的action creator 和 reducer

function showModal(modalId, args) {return {type: "nice-modal/show",payload: {modalId,args}}
}function hideModal(modalId, force) {return {type: "nice-modal/hide",payload: {modalId,force}}
}
const modalReducer = (state = { hiding: {} }, action) {switch (action.type) {case "nice-modal/show":const {modalId, args} = action.payloadreturn {...state,// 如果存在 modalId 对应的状态(即args),就显示这个对话框// 只要有参数就认为对话框应该显示,如果没有传递args,在reducer中使用默认值true[modalId]: args || true,// 定义一个hiding 状态, 用于处理对话框关闭动画hiding: {...state.hiding,[modalId]: false,}}case "nice-modal/hide":const { modalId, force: boolean } = action.payload// 只有force时才真正移除对话框,否则就是隐藏中hidingreturn action.payload.force ? {...state,[modalId]: false,hiding: { [modalId]: false }}: { ...state, hiding: { [modalId]: true } }default:return state}
}

这段代码的主要思路就是通过Redux的store去存储每个对话框状态和参数。在这里设计了两个action,分别显示和隐藏对话框。

特别注意的是,这里加入了hiding这样的一个状态,用来处理对话框关闭过程动画。

根据使用顺序,首先实现 createNiceModal,

使用容器模式,在对话框不可见时直接返回null,从而不渲染任何内容,

确保即使页面上定义了100个对话框,也不会影响性能。

createNiceModal = (modalId, Comp) => {return (props) => {const { visible, args } = useNiceModal(modalId)if (!visible) return nullreturn <Comp {...args} {...props} />}
}// 使用
const MyModal = createNiceModal('my-modal', () => {return (<NiceModal id="my-modal" title="Nice modal">Hello NiceModal</NiceModal>)
})

实现useNiceModal,根据id,封装一些逻辑。

让Redux的action使用起来更方便,在其内部封装对store的操作,从而实现对话框状态管理的逻辑重用。

const modalCallbacks = {}const useNiceModal = (modalId) => {const dispatch = useDispatch()// 封装Redux action 用于显示对话框const show = useCallback((args) => {dispatch(showModal(modalId, args))},[dispatch, modalId])// 封装Redux action 用于隐藏对话框 (force: boolean)const hide = useCallback((force) => {dispatch(hideModal(modalId, force))},[dispatch, modalId])const args = useSelector((s) => s[modalId])const hiding = useSelector((s) => s.hiding[modalId])// 只要有参数就认为对话框应该显示,如果没有传递args,在reducer中使用默认值truereturn { args, hiding, visible: !!args, show, hide }
}

这样,我们就实现了一个NiceModal这样的全局对话管理框架。

这样使用:

import { Button } from 'antd'
import NiceModal, {createNiceModal,useNiceModal
} from "./NiceModal"const MyModal = createNiceModal("my-modal", () => {return (<NiceModal id="my-modal" title="Nice Modal">Hello World</NiceModal>)
})function MyModalExample() {const modal = useNiceModal("my-modal")return (<><Button type="primary" onClick={() => modal.show()}>Show my modal</Button><MyModal /></>)
}

处理对话框的返回值


如果说对话框和页面这两种UI模式基本上是一致的,都是独立窗口完成独立逻辑。但是在用户交互上,有一定的差别:

  • 对话框可能需要返回值给调用者

  • 而页面切换一般不会关心页面执行的结果是什么

基于上面的NiceModal实现逻辑,现在考虑如何让调用者获得返回值。

我们可以把用户在对话框中的操作看成一个异步操作逻辑,那么用户在完成对话框中内容的操作后,就认为异步操作逻辑完成了。因此我们可以利用Promise来完成这样的逻辑。

那么,我们要实现的API如下:

const modal = useNiceModal('my-modal')
// 实现一个 promise API 来处理返回值
modal.show(args).then(res => {})

事实上,要实现这样一个机制并不困难,就是在 useNiceModal 这个 Hook 的实现中提供一个 modal.resolve 这样的方法,能够去 resolve modal.show 返回的 Promise。

代码的核心思路就是将show 和 resolve 两个函数通过 Promise 联系起来。因此两个函数调用位置不一样,所以我们使用一个局部的临时变量,来存放resolve回调函数。

// 使用一个 object 缓存 promise 的 resolve 回调函数
const modalCallbacks = {};
export const useNiceModal = (modalId) => {const dispatch = useDispatch();const show = useCallback((args) => {return new Promise((resolve) => {// 显示对话框时,返回 promise 并且将 resolve 方法临时存起来modalCallbacks[modalId] = resolve;dispatch(showModal(modalId, args));});},[dispatch, modalId],);const resolve = useCallback((args) => {if (modalCallbacks[modalId]) {// 如果存在 resolve 回调函数,那么就调用modalCallbacks[modalId](args);// 确保只能 resolve 一次delete modalCallbacks[modalId];}},[modalId],);// 其它逻辑...// 将 resolve 也作为返回值的一部分return { show, hide, resolve, visible, hiding };
};

React hook实现展示对话框相关推荐

  1. 源码解析 React Hook 构建过程

    2018 年的 React Conf 上 Dan Abramov 正式对外介绍了React Hook,这是一种让函数组件支持状态和其他 React 特性的全新方式,并被官方解读为这是下一个 5 年 R ...

  2. hook xposed 自定义类_【开始学习React Hook(1)】Hook之useState

    react hook是react推出的一种特殊函数.这些函数可以让你在不创建react class的情况下依然可以使用react的一些特性(诸如目前react的钩子函数拥有的所有特性). 最常用的ho ...

  3. React hook 中的数据获取

    相关说明: 对于hook相关词不翻译,感觉翻译后怪怪的. effect hook 效果钩子,用于执行一些副作用例如获取数据 . state hook 状态钩子. 使用----------- 和 --- ...

  4. slqite3库查询数据处理方式_绝活!十一个优质React Hook库, 收藏备用

    本文字数:6539字 预计阅读时间:18分钟 建议阅读方式:收藏备用 温馨提示:最近全国大幅降温,注意防寒保暖,开心跨年 长按识别,后台回复 "电子书" 即可领取<JavaS ...

  5. 尝鲜用 React Hook + Parcel 构建真心话大冒险简单页面

    首发于我的 Blog 阅读推荐:本人需要您有一定的 React 基础,并且想简单了解一下 Hook 的工作方式和注意点.但是并不详细介绍 React Hook,如果想有进一步的了解,可以查看官方文档. ...

  6. React Hook的用法: State + Effect(一)

    React Hook 简述 React Hook 是React 16.8 这个版本新增的一个特性.在此之前我们编写React组件一般大多数都是用 class组件,而非函数组件,因为函数组件不具有cla ...

  7. React Hook + Typescript,实现高颜值在线记账本

    React 已经是 JavaScript 生态系统中最受欢迎的前端框架之一.尽管人们已经对它赞不绝口,但 React 团队仍然在努力让它变得更好. 在 2018 ReactConf 大会上,React ...

  8. React Hook的用法: Contex + Reducer(二)

    React Hook 简述 通过上篇文章我们知道使用 State Hook 和 Effect Hook 可以让函数组件也能够具有自己的状态和在组件的各个阶段提供钩子暴露给开发者使用(点击我查看 Sta ...

  9. 【笔记-node】《Egg.js框架入门与实战》、《用 React+React Hook+Egg 造轮子 全栈开发旅游电商应用》

    20210226-20210227:<Egg.js框架入门与实战> 课程地址:https://www.imooc.com/learn/1185 第一章 课程导学 01-01 课程介绍 一. ...

最新文章

  1. Zookeeper和 Google Chubby对比分析
  2. 使用Python+turtle绘制同心圆
  3. arcgis引用样式无符号_【技术积累】arcgis制图应用:符号制作
  4. python中要使用导入全部的是什么符号-在python格式字符串中使用标点符号
  5. Pentium 4处理器架构/微架构/流水线 (9) - NetBurst执行核详解 - 执行单元与发射口
  6. dns设置邮箱服务器,专业版DNS设置-更多-Coremail论客邮件系统-企业邮箱,8亿用户信赖的邮件服务器系统...
  7. jquery的pagination插件实现无刷新的分页
  8. [C/C++] C++笔试常见问题
  9. Oracle 触发器 判断
  10. 从小白到大牛,程序员必读的经典套系书
  11. 计算机硬盘序列号有什么意义,硬盘序列号会/为什么会改变
  12. nvidia 卸载驱动
  13. Keep in mind用法
  14. python写邮箱系统登录_Python selenium登录163邮箱示例
  15. html页面导出word文档
  16. 可以修饰的基团有:氨基类,NHBOC类,Fmoc类不等,DSPE-PEG7-Mal
  17. Python-OpenCV-PS油画滤镜效果
  18. 【数据库】PostgreSQL简介
  19. React-SSR-Nextjs
  20. silverlight | chrion.exe-动态语言创建silverlight

热门文章

  1. 递归的逻辑(5)——米诺斯的迷宫
  2. 搞水产的人都笑了,智慧水产养殖水质监测解决方案
  3. 解决swiper滑动效果为fade的时候,内容重叠问题
  4. lammps教程:hcp类型晶格建模缺陷及解决方案
  5. C++ 11 tie关联输入输出流
  6. 由cadence allegro 设计的STM32开发板PCB教程
  7. 大概是全网最详细的何恺明团队顶作MoCo系列解读...(完结篇)
  8. 代价函数的系数中的二分之一做什么的
  9. windows7下开启telnet功能,解决win7下不能telnet的设置方案
  10. 网络爬虫——前程无忧网数据获取及存储