百度推广负责做网站吗,天津市城乡建设网,百度推广系统营销平台,设计制作的基本步骤是什么React造轮系列#xff1a;对话框组件 - Dialog 思路对话框一般是我们点击按钮弹出的这么一个东西#xff0c;主要类型有 Alter, Confirm 及 Modal, Modal 一般带有半透明的黑色背景。当然外观可参考 AntD 或者 Framework 等。确定 APIAPI 方面主要还是要参考同行#xff0c;…React造轮系列对话框组件 - Dialog 思路对话框一般是我们点击按钮弹出的这么一个东西主要类型有 Alter, Confirm 及 Modal, Modal 一般带有半透明的黑色背景。当然外观可参考 AntD 或者 Framework 等。确定 APIAPI 方面主要还是要参考同行因为如果有一天别人想你用的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.tsximport React, {useState} from reactimport Dialog from ./dialogexport default function () { const [x, setX] useState(false) return ( {setX(!x)}}点击 )}dialog/dialog.tsximport React from reactinterface Props { visible: boolean}const Dialog: React.FunctionComponent (props) { return ( props.visible ? dialog : null )}export default Dialog运行效果显示内容上述还有问题我们 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 reactimport ./dialog.scss;interface Props { visible: boolean}const Dialog: React.FunctionComponent (props) { return ( props.visible ? {props.children} : null )}export default Dialog完善头部内容及底部这里不多说直接上代码import React, {Fragment} from reactimport ./dialog.scss;import {Icon} from ../indexinterface 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 }运行起来你会发现有个警告主要是说我们渲染数组时需要加个 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)}}点击...运行效果发现遮罩并没有遮住 666 的内容。这是为什么看结构也很好理解遮罩元素与 666 是同级结构且层级比 666 低当然是覆盖不了的。那咱们可能就会这样做给 .fui-dialog-mask设置一个 zIndex 比它大的呗如 9999。效果恩感觉没问题这时我们在 Dialog 组件在嵌套一层 zIndex 为 9 的呢如 ... 运行效果如下发现父元素被压住了里面元素 zIndex 值如何的高都没有效果。那这要怎么破答案是不要让它出现在任何元素的里面这怎么可能呢。这里就需要引出一个神奇的 API了。这个 API 叫做 传送门(portal)。用法如下return ReactDOM.createPortal( this.props.children, domNode);第一个参数就是你的 div第二个参数就是你要去的地方。import React, {Fragment, ReactElement} from reactimport ReactDOM from react-domimport ./dialog.scss;import {Icon} from ../indeximport {scopedClassMaker} from ../classesinterface 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运行效果当然这样如果 Dialog 层级比同级的 zIndex 小的话还是覆盖不了。 那 zIndex 一般设置成多少比较合理。一般 Dialog 这层设置成 1, mask 这层设置成 2。定的越小越好因为用户可以去改。zIndex 的管理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}...运行效果但有个问题因为对话框的 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 之 confirmconfirm 调用方式 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 之 modalmodal 调用方式 {modal(你好)}}modalmodal 对应传递的内容就不是单单的文本了而是元素。实现方式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 类型。运行效果这还有个问题如果需要加按钮呢可能会这样写 {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 中各自的方法从表格可以看出modal 与其它两个只多了一个 retrun api,其实其它两个也可以返回对应的 Api只是我们没去调用而已所以补上这样一来这三个函数从抽象层面上来看是类似的所以这三个函数应该合成一个。首先抽取公共部分先取名为 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 定义。总结scopedClass 高阶函数的使用传送门 portal动态生成组件闭包传 API本组件为使用优化样式如果有兴趣可以自行优化本节源码已经上传至这里(https://github.com/qq449245884/frank-react2-test)中的 lib/dialog。