代码地址以下:<br>http://www.demodashi.com/demo/12315.htmlcss
注:本文Demo环境使用的是我平时开发用的配置:这里是地址。html
npm install
npm run start
npm run startfe
localhost:8088
查看demoModal组件是属于一个网站中比较经常使用的基础组件,可是在实现方面上稍微复杂一些,对场景支持的需求度较高。node
这里是Antd中Modal组件的演示Demo。react
首先分析这个组件的组成结构:webpack
其次,这个弹层不能生硬的出现,因此必定要有动画效果。css3
最后,弹层是在合适的地方经过用户交互的形式出现的,因此又一个控制器来控制Modal弹层的出现和关闭。git
首先来思考如何实现静态组件部分的代码。es6
先在components下面建立咱们的modal组件结构。github
这里样式文件使用scss,若是不熟悉的同窗可使用css代替或者先学习一下scss语法规则。web
在modal.js
中建立出组件的基础部分。
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import './modal.scss'; export default class Modal extends Component { constructor(props) { super(props); } render() { return ( <div>Modal</div> ); } } Modal.propTypes = {}; Modal.defaultProps = {};
接下来分析咱们的组件都须要预留哪些接口:
目前能想到的接口有这些,接下来咱们能够补充一下咱们的代码。
// 刚才的代码部分 Modal.propTypes = { isOpen: PropTypes.bool.isRequired, title: PropTypes.string.isRequired, children: PropTypes.oneOfType([PropTypes.element, PropTypes.string]).isRequired, className: PropTypes.string, maskClosable: PropTypes.bool, onCancel: PropTypes.func, onOk: PropTypes.func, okText: PropTypes.string, cancelText: PropTypes.string }; Modal.defaultProps = { className: '', maskClosable: true, onCancel: () => {}, onOk: () => {}, okText: 'OK', cancelText: 'Cancel' };
定义好接口以后,咱们能够根据咱们的接口来完善一下Modal组件。
export default class Modal extends Component { constructor(props) { super(props); this.state = { isOpen: props.isOpen || false }; } componentWillReceiveProps(nextProps) { if('isOpen' in nextProps) { this.setState({ isOpen: nextProps.isOpen }); } } render() { const { title, children, className, okText, cancelText, onOk, onCancel, maskClosable } = this.props; return ( <div className={`mocal-container ${className}`}> <div className="modal-body"> <div className={`modal-title ${type}`}>{title}</div> <div className="modal-content">{children}</div> <div className="modal-footer"> <button className="ok-btn" onClick={onOk}>{okText}</button> <button className="cancel-btn" onClick={onCancel}>{cancelText}</button> </div> </div> </div> ); } }
接下来是Modal组件的样式:
.modal-container { background-color: rgba(33, 33, 33, .4); position: fixed; top: 0; left: 0; right: 0; bottom: 0; opacity: 1; .modal-body { background-color: #fff; border-radius: 5px; padding: 30px; width: 400px; position: absolute; left: 50%; top: 40%; transform: translate3d(-50%, -50%, 0); .modal-title { text-align: center; font-size: 18px; font-weight: bold; } .modal-content { min-height: 100px; } .modal-footer { text-align: center; button { margin: 0 20px; padding: 8px 27px; font-size: 16px; border-radius: 2px; background-color: #ffd900; border: 0; outline: none; &:hover { cursor: pointer; background-color: #fff000; } } } } }
基础部分写完以后,咱们能够来验证一下本身的组件是否可以正常运行了。
咱们在直接在containers里面的hello里面引入Modal测试便可:
import React, { Component } from 'react'; import Modal from 'components/modal'; export default class Hello extends Component { render() { return ( <Modal title="Demo" okText="确认" cancelText="取消" > <div>Hello world!</div> </Modal> ); } }
node启动开发机,登陆到localhost:8088
,能够看到咱们的组件运行良好:
可是彷佛仍是有一点瑕疵,咱们的Modal不可能只有一个状态,所以咱们须要一个type接口,来控制咱们显示哪种Modal,好比success、error等。
继续改造Modal.js
:
Modal.PropTypes = { // ... type: PropTypes.oneOf(['alert', 'confirm', 'error']) }; Modal.defaultProps = { // ... type: 'alert', };
咱们在scss中稍微改变一点样式,能让咱们分辨出来。 基本上都是使用特定的icon图片来做区分,这里为了简化代码量,直接使用emoji字符来代替了。
.modal-title { // ... &.error:before { content: '❌'; display: inline-block; } &.success:before { content: '✔'; color: rgb(75, 231, 14); display: inline-block; } &.confirm:before { content: '❓'; display: inline-block; } &.alert:before { content: '❕'; display: inline-block; } }
如今在看咱们的组件,能够看到已经有区分度了:
正常状况下,咱们会继续细分不少东西,好比什么状况下不显示按钮组,什么状况下只显示确认按钮等。这里就不进行细分工做了。
Modal组件的骨架搭好以后,咱们能够开始考虑组件须要的方法了。
首先组件是要能够关闭的,而且咱们不管点击确认或者取消或者黑色弹层都要能够关闭组件。
并且当咱们组件打开的时候,须要给body加上类名,方便咱们以后的一切操做。
const modalOpenClass = 'modal-open'; const toggleBodyClass = isOpen => { const body = document.body; if(isOpen) { body.classList.add(modalOpenClass); } else { body.classList.remove(modalOpenClass); } } export default class Modal extends Component { /// ... constructor(props) { // ... toggleBodyClass(props.isOpen); } // 关闭弹层函数 close() { this.setState() { isOpen: false }; toggleBodyClass(false); } // 点击确认回调函数 onOkClick() { this.props.onOk(); this.close(); } // 点击取消的回调函数 onCancelClick() { this.props.onCancel(); this.close(); } // ... }
这些函数由于都要绑定到dom节点上,所以要提早绑定this,所以咱们能够写一个工具函数,建立一个lib
文件夹,在lib
下建立一个util.js
文件。
// lib/util export default { bindMethods(methods, obj) { methods.forEach(func => { if(typeof func === 'function') { obj[func] = obj[func].bind(this); } }) } }
而后在咱们的Modal组件中引入util文件,绑定函数的this。
// Modal.js import util from 'lib/util'; // ... constructor(props) { // ... util.bindMethods(['onCancelClick', 'onOkClick', 'close'], this); } // ...
而后咱们就能够将刚才的点击函数都替换掉:
render() { // ... return ( <div className={`mocal-container ${className}`} onClick={maskClosable ? this.close : () => {}}> <div className="modal-body"> <div className={`modal-title ${type}`}>{title}</div> <div className="modal-content">{children}</div> <div className="modal-footer"> <button className="ok-btn" onClick={this.onOkClick}>{okText}</button> <button className="cancel-btn" onClick={this.onCancelClick}>{cancelText}</button> </div> </div> </div> ); }
去实验一下代码,发现确实能够关闭了。
Modal组件主体部分写完以后,咱们还要考虑考虑实际业务场景。
咱们都知道React是一个组件化的框架,咱们写好这个Modal组件后,不多是将这个组件嵌套在其余组件内部使用的,而是要直接在body下面占满全屏显示,因此写到这里为止是确定不够的。
而且在网站中,通常都是有一个按钮,当用户点击以后,才弹出Modal提示用户。
所以,咱们如今这种经过组件调用的方式是确定不行的,所以还要对这个Modal组件进行封装。
在modal
目录下建立一个index.js
文件,表明咱们整个Modal组件的入口文件。
而后在index.js
中书写咱们的主要控制器代码:
// index.js import React from 'react'; import ReactDOM from 'react-dom'; import Modal from './modal'; const show = (props) => { let component = null; const div = document.createElement('div'); document.body.appendChild(div); const onClose = () => { ReactDOM.unmountComponentAtNode(div); document.body.removeChild(div); if(typeof props.onClose === 'function') { props.onClose(); } } ReactDOM.render( <Modal {...props} onClose={onClose} ref={c => component = c} isOpen >{props.content}</Modal>, div ); return () => component.close(); } const ModalBox = {}; ModalBox.confirm = (props) => show({ ...props, type: 'confirm' }); ModalBox.alert = (props) => show({ ...props, type: 'alert' }); ModalBox.error = (props) => show({ ...props, type: 'error' }); ModalBox.success = (props) => show({ ...props, type: 'success' }); export default ModalBox;
这段控制器的代码比较简单。
show
函数用来控制Modal组件的显示,当show以后,在body下面建立一个div,而后将Modal组件熏染到这个div下面,而且在删除的时候一块儿将div和Modal组件都删除掉。
ModalBox
就负责咱们平时动态调用,根据咱们传入不一样的type
值而显示不一样type的Modal组件。
如今咱们能够去改造一下container的入口文件了:
// hello.js import React, { Component } from 'react'; import Modal from 'components/modal'; export default class Hello extends Component { render() { return ( <div> <button onClick={() => Modal.confirm({ title: 'Demo', content: 'Hello world!', okText: '确认', cancelText: '取消', onOk: () => console.log('ok'), onCancel: () => console.log('cancel') })}>click me!</button> </div> ); } }
到此为止,咱们点击click me
的按钮以后,能够正常显示和关闭Modal组件了,而且点击确认和取消按钮的时候,都会调用相对应的回调函数来显示'ok' 'cancel'
字样。
生硬的Modal组件天然不是咱们最终追求的效果,因此咱们还要加上最后一个部分:动画效果。
React实现动画的方式有不少,可是总结起来可能只有两种:
在复杂动画的状况下,通常选择第二种,所以我这里也是使用第三方react动画库来实现Modal的动画效果。
考虑到动画结束,删除组件以后还应该有一个回调函数,所以这里采用的是react-motion动画库,而不是常见的CSSTransitionGroup动画库。
在增长动画效果以前,咱们要增长一个刚才提到的动画结束以后的回调函数,所以还须要增长一个接口。
onRest: PropTypes.func
而且将这个接口的默认值改成空函数:
onRest: () => {}
这里就不介绍具体的react-motion的使用方法了,直接展现最终的代码:
import { Motion, spring, presets } from 'react-motion'; export default class Modal extends Component { constructor(props) { // ... util.bindMethods(['onCancelClick', 'onOkClick', 'close', 'onRest'], this); } // ... // 动画结束以后的回调函数 onRest() { const { isOpen } = this.state; if(!isOpen) { this.props.onClose(); } this.props.onRest(); } render() { // ... return ( <Motion defaultStyle={{ opacity: 0.8, scale: 0.8 }} style={{ opacity: spring(isOpen ? 1 : 0, presets.stiff), scale: spring(isOpen ? 1 : 0.8, presets.stiff) }} onRest={this.onRest} > { ({ opacity, scale }) => ( <div className={`modal-container ${className}`} style={{opacity}} onClick={maskClosable ? this.close : () => {}} > <div className="modal-body" style={{ opacity, transform: `translate3d(-50%, -50%, 0) scale(${scale})` }} > <div className={`modal-title ${type}`}>{title}</div> <div className="modal-content">{children}</div> <div className="modal-footer"> <button className="ok-btn" onClick={this.onOkClick}>{okText}</button> <button className="cancel-btn" onClick={this.onCancelClick}>{cancelText}</button> </div> </div> </div> ) } </Motion> ); } }
到此为止,整个Modal组件就已经完成了,但愿这份demo对学习react的同窗有所帮助。
在设计基础组件的时候,必定要尽量多的考虑业务场景,而后根据业务场景去设计接口,尽可能保证基础组件可以在全部的场景中均可以正常使用。
这份Demo是在React15.6.0版本下书写的,由于React已经升级到16版本,而且16增长了新的createPortal()
方法,因此Modal组件的实现方式会有所变化,具体的实现方法在下一篇文章介绍。React15.6.0实现Modal弹层组件
代码地址以下:<br>http://www.demodashi.com/demo/12315.html
注:本文著做权归做者,由demo大师代发,拒绝转载,转载须要做者受权