react antd confirm content list_React造轮系列:对话框组件 - Dialog 思路

React造轮系列:对话框组件 - Dialog 思路

61f0ab970fc6873491170a11da65f660.png

对话框一般是我们点击按钮弹出的这么一个东西,主要类型有 Alter, Confirm 及 Modal, Modal 一般带有半透明的黑色背景。当然外观可参考 AntD 或者 Framework 等。

确定 API

API 方面主要还是要参考同行,因为如果有一天,别人想你用的UI框架时,你的 API 跟他之前常用的又不用,这样就加大了入门门槛,所以API 尽量保持跟现有的差不多。

对话框除了提供显示属性外,还要有点击确认后的回放函数,如:

alert('你好').then(fn)confirm('确定?').then(fn)modal(组件名)

实现

Dialog 源码已经上传到这里(https://github.com/qq449245884/frank-react2-test)。

dialog/dialog.example.tsx, 这里 state ,生命周期使用 React 16.8 新出的 Hook,如果对 Hook 不熟悉可以先看官网文档。

dialog/dialog.example.tsx

import React, {useState} from 'react'import Dialog from './dialog'export default function () { const [x, setX] = useState(false) return ( 
{setX(!x)}}>点击
)}

dialog/dialog.tsx

import React from 'react'interface Props { visible: boolean}const Dialog: React.FunctionComponent = (props) => { return ( props.visible ?  
dialog
: null )}export default Dialog

运行效果

b097138ecac30eeea3b5091a64cb409d.gif

显示内容

上述还有问题,我们 dialog 在组件内是写死的,我们想的是直接通过组件内包裹的内容,如:

// dialog/dialog.example.tsx...hi...

这样写,页面上是不会显示 hi 的,这里 children 属性就派上用场了,我们需要在 dialog 组件中进一步骤修改如下内容:

// dialog/dialog.tsx...return ( props.visible ?  
{props.children}
: null)...

显示遮罩

通常对话框会有一层遮罩,通常我们大都会这样写:

// dialog/dialog.tsx...props.visible ?  
{props.children}
: null...

这种结构有个不好的地方就是点击遮罩层的时候要关闭对话框,如果是用这种结构,用户点击任何 div,都相当于点击遮罩层,所以最好要分开:

// dialog/dialog.tsx...
{props.children}
...

由于 React 要求最外层只能有一个元素, 所以我们多用了一个 div 包裹起来,但是这种方法无形之中多了个 div,所以可以使用 React 16 之后新出的 Fragment, Fragment 跟 vue 中的 template 一样,它是不会渲染到页面的。

import React, {Fragment} from 'react'import './dialog.scss';interface Props { visible: boolean}const Dialog: React.FunctionComponent = (props) => { return ( props.visible ?  
{props.children}
: null )}export default Dialog

完善头部,内容及底部

这里不多说,直接上代码

import React, {Fragment} from 'react'import './dialog.scss';import {Icon} from '../index'interface Props { visible: boolean}const Dialog: React.FunctionComponent = (props) => { return ( props.visible ?  
提示 {props.children} ok cancel
: null )}export default Dialog

从上述代码我们可以发现我们写样式的名字时候,为了不被第三使用覆盖,我们自定义了一个 fui-dialog前缀,在写每个样式名称时,都要写一遍,这样显然不太合理,万一哪天我不用这个前缀时候,每个都要改一遍,所以我们需要一个方法来封装。

咱们可能会写这样方法:

function scopedClass(name) { return `fui-dialog-${name}`}

这样写不行,因为我们 name 可能不传,这样就会多出一个 -,所以需要进一步的判断:

function scopedClass(name) { return `fui-dialog-${name ? '-' + name : ''}`}

那还有没有更简洁的方法,使用 filter 方法:

function scopedClass(name ?: string) { return ['fui-dialog', name].filter(Boolean).join('-')}

调用方式如下:

...
提示 {props.children} ok cancel
...

大家在想法,这样写是有问题,每个组件都写一个函数吗,如果 Icon 组件,我还需要写一个fui-icon, 解决方法是把 前缀当一个参数,如:

function scopedClass(name ?: string) { return ['fui-dialog', name].filter(Boolean).join('-')}

调用方式如下:

className={scopedClass('fui-dialog', 'mask')}

这样写,还不如直接写样式,这种方式是等于白写了一个方法,那怎么办?这就需要高阶函数出场了。实现如下:

function scopeClassMaker(prefix: string) { return function (name ?: string) { return [prefix, name].filter(Boolean).join('-') }}const scopedClass = scopeClassMaker('fui-dialog')

scopeClassMaker 函数是高级函数,返回一个带了 prefix 参数的函数。

事件处理

在写事件处理之前,我们 Dialog 需要接收一个 buttons 属性,就是显示的操作按钮并添加事件:

// dialog/dialog.example.tsx... {setX(false)}}>1,  {setX(false)}}>2, ]}> 
hi
...

咱们看到这个,第一反应应该是觉得这样写很麻烦,我写个 dialog, visible要自己,按钮要自己,连事件也要自己写。请接受这种设定。虽然麻烦,但非常的好理解。这跟 Vue 的理念是不太一样的。当然后面会进一步骤优化。

组件内渲染如下:

 { props.buttons }

运行起来你会发现有个警告:

102582781af692fd3dbc3d0aacbcb136.png

主要是说我们渲染数组时,需要加个 key,解决方法有两种,就是不要使用数组方式,当然这不治本,所以这里 React.cloneElemen 出场了,它可以克隆元素并添加对应的属性值,如下:

{ props.buttons.map((button, index) => { React.cloneElement(button, {key: index}) })}

对应的点击关闭事件相对容易这边就不讲了,可以自行查看源码。

接下来来看一个样式的问题,首先先给出我们遮罩的样式:

.fui-dialog { position: fixed; background: white; min-width: 20em; z-index: 2; border-radius: 4px; top: 50%; left: 50%; transform: translate(-50%, -50%); &-mask { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: fade_out(black, 0.5); z-index: 1; } .... 以下省略其它样式}

我们遮罩 .fui-dialog-mask 使用 fixed 定位感觉是没问题的,那如果在调用 dialog 同级在加以下这么元素:

666
{setX(!x)}}>点击...

运行效果:

0a5734405e95c5119c50b24317abf595.png

发现遮罩并没有遮住 666 的内容。这是为什么?

85902ca60f14c3d9dbf1e470cd39be6e.png

看结构也很好理解,遮罩元素与 666 是同级结构,且层级比 666 低,当然是覆盖不了的。那咱们可能就会这样做,给 .fui-dialog-mask设置一个 zIndex 比它大的呗,如 9999。

效果:

8a5af188c58abb3bcf51a25fd41afa80.png

恩,感觉没问题,这时我们在 Dialog 组件在嵌套一层 zIndex 为 9 的呢,如:

...

运行效果如下:

c2b5f94750f7b3eadec0218845d0530f.png

发现,父元素被压住了,里面元素 zIndex 值如何的高,都没有效果。

那这要怎么破?答案是不要让它出现在任何元素的里面,这怎么可能呢。这里就需要引出一个神奇的 API了。这个 API 叫做 传送门(portal)。

用法如下:

return ReactDOM.createPortal( this.props.children, domNode);

第一个参数就是你的 div,第二个参数就是你要去的地方。

import React, {Fragment, ReactElement} from 'react'import ReactDOM from 'react-dom'import './dialog.scss';import {Icon} from '../index'import {scopedClassMaker} from '../classes'interface Props { visible: boolean, buttons: Array, onClose: React.MouseEventHandler, closeOnClickMask?: boolean}const scopedClass = scopedClassMaker('fui-dialog')const sc = scopedClassconst Dialog: React.FunctionComponent = (props) => { const onClickClose: React.MouseEventHandler = (e) => { props.onClose(e) } const onClickMask: React.MouseEventHandler = (e) => { if (props.closeOnClickMask) { props.onClose(e) } } const x = props.visible ?  
提示 {props.children} { props.buttons.map((button, index) => { React.cloneElement(button, {key: index}) }) }
: null return ( ReactDOM.createPortal(x, document.body) )}Dialog.defaultProps = { closeOnClickMask: false}export default Dialog

运行效果:

09ab5cf85d2cb75eaad35372a6afee3a.png

当然这样,如果 Dialog 层级比同级的 zIndex 小的话,还是覆盖不了。 那 zIndex 一般设置成多少比较合理。一般 Dialog 这层设置成 1, mask 这层设置成 2。定的越小越好,因为用户可以去改。

zIndex 的管理

919b1af42ad19fdb4abd5e99d8999d11.png

zIndex 管理一般就是前端架构师要做的了,根据业务产景来划分,如广告肯定是要在页面最上面,所以 zIndex 一般是属于最高级的。

便利的 API 之 Alert

上述我们使用 Dialog 组件调用方式比较麻烦,写了一堆,有时候我们想到使用 alert 直接弹出一个对话框这样简单方便。如

 

example 3

alert('1')}>alert

我们想直接点击 button ,然后弹出我们自定义的对话框内容为1 ,需要在 Dialog 组件内我们需要导出一个 alert 方法,如下:

// dialog/dialog.tsx...const alert = (content: string) => { const component =  {}}> {content}  const div = document.createElement('div') document.body.append(div) ReactDOM.render(component, div)}export {alert}...

运行效果:

3bbd941ebfdeb10150a8e0fe22d46d9e.gif

但有个问题,因为对话框的 visible 是由外部传入的,且 React 是单向数据流的,在组件内并不能直接修改 visible,所以在 onClose 方法我们需要再次渲染一个新的组件,并设置新组件 visible 为 ture,覆盖原来的组件:

...const alert = (content: string) => { const component =  { ReactDOM.render(React.cloneElement(component, {visible: false}), div) ReactDOM.unmountComponentAtNode(div) div.remove() }}> {content}  const div = document.createElement('div') document.body.append(div) ReactDOM.render(component, div)}..

便利的 API 之 confirm

confirm 调用方式:

 confirm('1', ()=>{}, ()=> {})}>confirm

第一个参数是显示的内容,每二个参数是确认的回调,第三个参数是取消的回调函数。

实现方式:

const confirm = (content: string, yes?: () => void, no?: () => void) => { const onYes = () => { ReactDOM.render(React.cloneElement(component, {visible: false}), div) ReactDOM.unmountComponentAtNode(div) div.remove() yes && yes() } const onNo = () => { ReactDOM.render(React.cloneElement(component, {visible: false}), div) ReactDOM.unmountComponentAtNode(div) div.remove() no && no() } const component = (  { onNo()}} buttons={[yes,  no ]} > {content} ) const div = document.createElement('div') document.body.appendChild(div) ReactDOM.render(component, div)}

事件处理跟 Alter 差不多,唯一多了一步就是 confirm 当点击 yes 或者 no 的时候,如果外部有回调就需要调用对应的回调函数。

便利的 API 之 modal

modal 调用方式:

 {modal(

你好

)}}>modal

modal 对应传递的内容就不是单单的文本了,而是元素。

实现方式:

const modal = (content: ReactNode | ReactFragment) => { const onClose = () => { ReactDOM.render(React.cloneElement(component, {visible: false}), div) ReactDOM.unmountComponentAtNode(div) div.remove() } const component =  {content}  const div = document.createElement('div') document.body.appendChild(div) ReactDOM.render(component, div)}

注意,这边的 content 类型。

运行效果:

c848072eec4511f02d5794158637aee8.gif

这还有个问题,如果需要加按钮呢,可能会这样写:

  {modal(

你好 close

)}}>modal

这样是关不了的,因为 Dialog 是封装在 modal 里面的。如果要关,必须控制 visible,那很显然我从外面控制不了里面的 visible,所以这个 button 没有办法把这个 modal 关掉。

解决方法就是使用闭包,我们可以在 modal 方法里面把 close 方法返回:

const modal = (content: ReactNode | ReactFragment) => { const onClose = () => { ReactDOM.render(React.cloneElement(component, {visible: false}), div) ReactDOM.unmountComponentAtNode(div) div.remove() } const component =  {content}  const div = document.createElement('div') document.body.appendChild(div) ReactDOM.render(component, div) return onClose;}

最后多了一个 retrun onClose,由于闭包的作用,外部调用返回的 onClose 方法可以访问到内部变量。

调用方式:

const openModal = () => { const close = modal(

你好 close()}>close

)}modal

重构 API

在重构之前,我们先要抽象 alert, confirm, modal 中各自的方法:

afd28b6c113c0c7cca7666b46b662c43.png

从表格可以看出,modal 与其它两个只多了一个 retrun api,其实其它两个也可以返回对应的 Api,只是我们没去调用而已,所以补上:

dab342e4648ff1803c9c5ee44d43f7b3.png

这样一来,这三个函数从抽象层面上来看是类似的,所以这三个函数应该合成一个。

首先抽取公共部分,先取名为 x ,内容如下:

const x= (content: ReactNode, buttons ?:Array, afterClose?: () => void) => { const close = () => { ReactDOM.render(React.cloneElement(component, {visible: false}), div) ReactDOM.unmountComponentAtNode(div) div.remove() afterClose && afterClose() } const component =   { close(); afterClose && afterClose() }} buttons={buttons} > {content}  const div = document.createElement('div') document.body.append(div) ReactDOM.render(component, div) return close}

alert 重构后的代码如下:

const alert = (content: string) => { const button =  close()}>ok const close = x(content, [button])}

confirm 重构后的代码如下:

const confirm = (content: string, yes?: () => void, no?: () => void) => { const onYes = () => { close() yes && yes() } const onNo = () => { close() no && no() } const buttons = [ yes,  no ] const close = modal(content, buttons, no)}

modal 重构后的代码如下:

const modal = (content: ReactNode | ReactFragment) => { return x(content)}

最后发现其实 x 方法就是 modal 方法,所以更改 x 名为 modal,删除对应的 modal 定义。

总结

  1. scopedClass 高阶函数的使用
  2. 传送门 portal
  3. 动态生成组件
  4. 闭包传 API

本组件为使用优化样式,如果有兴趣可以自行优化,本节源码已经上传至这里(https://github.com/qq449245884/frank-react2-test)中的 lib/dialog。

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

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

相关文章

Spring IOC 和 AOP 概览

IOC(控制反转) IoC(Inversion of Control,控制倒转)。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。 在没有IOC时,我们通过new 等关键字等方…

Java 并发实践 — ConcurrentHashMap 与 CAS

转载自 Java 并发实践 — ConcurrentHashMap 与 CAS最近在做接口限流时涉及到了一个有意思问题,牵扯出了关于concurrentHashMap的一些用法,以及CAS的一些概念。限流算法很多,我主要就以最简单的计数器法来做引。先抽象化一下需求:…

git rebase命令(转)

转自&#xff1a; https://www.yiibai.com/git/git_rebase.html git rebase命令在另一个分支基础之上重新应用&#xff0c;用于把一个分支的修改合并到当前分支。 使用语法 git rebase [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>][<u…

python tkinter计算器实例_python -Tkinter 实现一个小计算器功能

原博文 2017-03-25 22:08 − 文章来源&#xff1a;http://www.cnblogs.com/Skyyj/p/6618739.html 本代码是基于python 2.7的 如果是对于python3.X 则需要将 tkinter 改为Tkinter 将tkMessagebox&... 相关推荐 2019-12-10 15:59 − python GUI编程(Tkinter) Python 提供了多…

Spring IOC 容器启动、Bean生命周期详解

前言 在Spring IOC 和 AOP 概览中&#xff0c;简要介绍了IOC容器和AOP&#xff0c;没有深入IOC容器Bean的实例化&#xff0c;此文承接上文深入分析Bean的实例化过程、生命周期。 Spring IOC的过程 Spring的IoC容器在实现控制反转和依赖注入的过程中,可以划分为两个阶段: 容…

java 8 Lambda 表达式(副作用)

【1】转自&#xff1a; https://www.cnblogs.com/linlinismine/p/9283532.html 早在2014年oracle发布了jdk 8,在里面增加了lambda模块。于是java程序员们又多了一种新的编程方式&#xff1a;函数式编程&#xff0c;也就是lambda表达式。我自己用lambda表达式也差不多快4年了&am…

Java NIO:浅析I/O模型

转载自 Java NIO&#xff1a;浅析I/O模型也许很多朋友在学习NIO的时候都会感觉有点吃力&#xff0c;对里面的很多概念都感觉不是那么明朗。在进入Java NIO编程之前&#xff0c;我们今天先来讨论一些比较基础的知识&#xff1a;I/O模型。下面本文先从同步和异步的概念 说起&…

ubuntu安装python3.8_Ubuntu 16.04 安装 python3.8

Ubuntu 16.04 amd64 (64bit)&#xff08;纯净版&#xff09; 自带python2.7和python3.5 执行"whereis python"查看当前安装的python [rootroot ~]# whereis python python: /usr/bin/python2.7 /usr/bin/python /usr/lib/python2.7 /usr/lib64/python2.7 /etc/python…

Spring IOC 如何解决循环依赖?

前言 假设对象A、B 之间相互依赖&#xff0c;Spring IOC是如何解决A、B两个对象的实例化的&#xff1f;答案是三级缓存。 三级缓存 SpringIOC 通过三级缓存来解决循环依赖问题&#xff0c;三级缓存指的是三个Map&#xff1a; singletonObjects&#xff1a;一级缓存&#xf…

pythondocx模板_使用python-docx-template修改word文档

由于最近工作中需要自动修改word文档&#xff0c;并生成PDF文件&#xff0c;经过查阅资料后发现使用python-docx-template可以完成对word的修改工作&#xff0c;于是记录一下使用方法。文章内容大部分来自对以下博客的整理和学习https://blog.csdn.net/weixin_42670653/article…

面试必问的 CAS ,要多了解

转载自 面试必问的 CAS &#xff0c;要多了解前言 CAS&#xff08;Compare and Swap&#xff09;&#xff0c;即比较并替换&#xff0c;实现并发算法时常用到的一种技术&#xff0c;Doug lea大神在java同步器中大量使用了CAS技术&#xff0c;鬼斧神工的实现了多线程执行的安全性…

MySQL 对于千万级的大表要怎么优化?

很多人第一反应是各种切分&#xff1b; 我给的顺序是: 第一 优化你的sql和索引&#xff1b; 第二 加缓存&#xff0c;memcached,redis&#xff1b; 第三 以上都做了后&#xff0c;还是慢&#xff0c;就做主从复制或主主复制&#xff0c;读写分离&#xff0c;可以在应用层做&…

MySQL元数据库——information_schema

转自&#xff1a; https://www.cnblogs.com/postnull/p/6697077.html 平时使用MySQL客户端操作数据库的同学&#xff0c;只要稍微留神都会发现&#xff0c;除了我们建的库之外&#xff0c;还经常看到三个数据库的影子&#xff1a; 1. information_schema 2. performance_sche…

mysql 表字段信息从一张表迁移到另一张表_MySQL(数据库)笔记

###数据库之前通过流去操作文件保存数据库的弊端:1.执行效率低2.开发成本高3.一般只能保存小量数据4.只能保存文本数据####什么是DB- DataBase 数据库:代表文件集合####什么是DBMS- DataBaseManagementSystem 数据库管理系统(软件),用于管理保存数据的文件集合,用于和程序员进行…

GET与POST传递数据的最大长度能够达到多少

各种web开发语言中&#xff0c;各个页面之间基本都会进行数据的传递&#xff0c;web开发里面比较常用的数据传递方式有get post&#xff0c;一直以来我都只知道get传递的数据量要比post传递的数据量要少&#xff0c;所以传递大数据量还是要用post&#xff0c;但是 get post 这两…

maven命令实战

【1】 创建maven项目 1&#xff09;目录结构 mavenhello09|---src|---|---main|---|---|---java|---|---|---resources|---|---test|---|---|---java|---|---|---resources|---pom.xml 目录结构说明&#xff1a; main/java&#xff1a;主程序&#xff1b;main/resources&…

Mac 环境变量配置

环境变量配置 cd ~ (回到主目录home)如果你是第一次配置环境变量&#xff0c;可以使用“touch .bash_profile” 创建一个.bash_profile的隐藏配置文件vim .bash_profile&#xff0c;写入相应的环境变量&#xff0c;如下&#xff1a; # golang配置 export GOROOT/usr/local/Ce…

python测试开发面试题_python测试开发面试之深浅拷贝

先来道题热热身 a (a, b,c) c copy.copy(a) d copy.deepcopy(a) if c d: print("c和d的值相等") if id(c) id(d): print("c和d的地址相等") 想想最后打印的是什么&#xff1f; 什么是深拷贝和浅拷贝 深拷贝&#xff0c;就是在对某个对象进行拷贝的时候…

linux虚拟机tomcat上部署web项目的常用命令

1&#xff09;查看 tomcat是否在运行 ps -ef | grep tomcat ps -ef 补充&#xff1a;Linux中的ps命令是Process Status的缩写&#xff0c;ps命令用来列出系统中当前运行的那些进程。ps命令可以列出当前进程的运行情况&#xff08;状态、时间等信息&#xff09;。在Linux系统中…

一文理清Cookie、Session、Token

发展史 1、很久很久以前&#xff0c;Web 基本上就是文档的浏览而已&#xff0c; 既然是浏览&#xff0c;作为服务器&#xff0c; 不需要记录谁在某一段时间里都浏览了什么文档&#xff0c;每次请求都是一个新的HTTP协议&#xff0c; 就是请求加响应&#xff0c; 尤其是我不用记…