代码地址如下:
http://www.demodashi.com/demo/12315.html

注:本文Demo环境使用的是我平时开发用的配置:这里是地址。

本文适合对象

  1. 了解React。
  2. 使用过webpack3。
  3. 熟悉es6语法。

项目说明

项目结构截图

项目运行说明

  1. npm install
  2. npm run start
  3. npm run startfe
  4. 登录localhost:8088查看demo

Modal组件分析

Modal组件是属于一个网站中比较常用的基础组件,但是在实现方面上稍微复杂一些,对场景支持的需求度较高。

这里是Antd中Modal组件的演示Demo。

首先分析这个组件的组成结构:
1. title Modal弹层的标题部分。
2. content Modal弹层的主体部分。
3. footer Modal弹层最后的button部分。
4. background 整个黑色背景

其次,这个弹层不能生硬的出现,所以一定要有动画效果。

最后,弹层是在合适的地方通过用户交互的形式出现的,所以又一个控制器来控制Modal弹层的出现和关闭。

Modal组件的实现

静态组件

首先来思考如何实现静态组件部分的代码。

先在components下面创建我们的modal组件结构。
- -components/
- -modal/
- -modal.js
- -modal.scss

这里样式文件使用scss,如果不熟悉的同学可以使用css代替或者先学习一下scss语法规则。

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 = {};

接下来分析我们的组件都需要预留哪些接口:
1. 开关状态isOpen
2. Modal标题title
3. Modal主体内容children
4. Modal类名className
5. 点击黑色区域是否可以关闭maskClosable
6. 关闭按钮文案 cancelText
7. 确认按钮文案 okText
8. 关闭按钮回调函数 onCancel
9. 确认按钮回调函数 onOk

目前能想到的接口有这些,接下来我们可以补充一下我们的代码。

// 刚才的代码部分
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.jsimport 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.jsimport 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实现动画的方式有很多,但是总结起来可能只有两种:
1. 使用css3实现动画。
2. 根据react的状态管理利用js实现动画。

在复杂动画的情况下,一般选择第二种,因此我这里也是使用第三方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弹层组件

代码地址如下:
http://www.demodashi.com/demo/12315.html

注:本文著作权归作者,由demo大师发表,拒绝转载,转载需要作者授权

React15.6.0实现Modal弹层组件相关推荐

  1. layer之弹层组件文档 layui.layer(v.1.9.0之后)

    弹层组件文档 - layui.layer layer 至今仍作为 layui 的代表作,她的受众广泛并非偶然,而是这数年来的坚持.不弃的执念,将那些不屑的眼光转化为应得的尊重,不断完善和维护.不断建设 ...

  2. 原生、无依赖的web弹层组件——HLayer.js

    经过两周的开发,今天HLayer.js终于完成了0.1.0版本,作为一个前端新人,更多的是将这个作为自己的练手作品,毕竟web弹层组件早已不是web开发的新鲜事物了,国内外也不乏众多优秀的弹层组件.不 ...

  3. jQuery Layer 弹层组件

    layer是一款近年来口碑非常不错的web弹层组件,她具备全方位的解决方案,致力于服务各个水平段的开发人员,您的页面会轻松地拥有丰富友好的操作体验. 在与同类组件的比较中,layer总是能轻易获胜.她 ...

  4. 一款近年来备受青睐的web弹层组件——layer(http://layer.layui.com/)

    一款近年来备受青睐的web弹层组件--layer (http://layer.layui.com/) 一.使用场景 由于layer可以独立使用,也可以通过Layui模块化使用.所以请按照实际需求来选择 ...

  5. layer-v2.4弹层组件使用示例

    弹出层layer的使用 Intro layer是一款web弹层组件,致力于服务各个水平段的开发人员.layer官网:http://layer.layui.com/ layer侧重于用户灵活的自定义,为 ...

  6. Web 弹层组件-layer

    Web 弹层组件layer 介绍 layer是一款优秀的基于jQuery的web弹层组件,目前GitHub 上Stars5000+. 相关地址 官网:http://layer.layui.com/?a ...

  7. layer真是一个不错的web弹层组件

    发现layer组件的那一刻, 被她的惊艳迷住了,立刻找到她的官网,各种试各种体验,很OK,以下是摘自官网的相关介绍. 一些唠叨 在线调试 前往旧版1.8.5 layer是一款近年来备受青睐的web弹层 ...

  8. layui.layer弹层组件

    layui.layer layui前端UI框架 layer弹层组件 layer使用方法 layer方法使用 layer.msg(content, options, end) - 提示框 layer.l ...

  9. vant组件搜索并选择_Vue.js自定义弹层组件|vue仿微信/ios弹窗

    最近趁着国庆假期给自己充充电,一直在捣鼓Nuxt.js项目,由于项目中需要用到定制化弹窗功能,本来是想着使用Vant组件库,经过再三考虑,最后决定还是自己重新造一个,于是就有了Vue自定义弹出层VPo ...

最新文章

  1. 【正一专栏】没有对比就没有伤害-恒大亚冠赛有感
  2. 简述ospf的工作原理_简述洛氏硬度计的工作原理及应用领域
  3. python 单例模式的四种实现方法
  4. LateX在windows中运用MiKTeX
  5. Js 怎么遍历json对象所有key及根据动态key获取值(根据key值获取相应的value值)...
  6. 计算机网络:overlay(VLAN,VxLAN)、underlay网络、大二层的介绍
  7. golang | 使用结构体抄的学生信息管理系统
  8. [转载] 杜拉拉升职记——34 设定工作目标要符合“SMART”原则
  9. 联发科被动“卡位”内地集成电路市场 剑指老对手展讯
  10. Python进阶之八皇后算法
  11. 将 SPSS 分析技术应用于大数据
  12. python抓取网络摄像头,Python抓取酒店针孔摄像头 防偷窥
  13. 你需要了解的JS框架 用途:构建数
  14. 树莓派3初始化安装(Raspberry Pi III)
  15. android媒体焦点音量压低/暂停逻辑源码简析
  16. SQL查询-查询所有员工的姓名及其直接上级的姓名,没有领导的员工也需要查询
  17. 软件工程领域CCF B类会议:SANER介绍(以SANER 2019为例)
  18. Oracle表分区.
  19. Java面试题集(116-135)
  20. 美国Zaytran夹具/焊接定位器

热门文章

  1. hive使用适用场景_Hive 中 Map Join 的适用场景:非等值连接
  2. python dlib人脸检测_使用Python+OpenCV+Dlib实现人脸检测与人脸特征关键点识别
  3. STM32F103_SPI读写外部FLASH
  4. Mind+实时模式智能问答机器人
  5. qt5中重绘工具栏_Qt ------ QPainter 和控件组件的重绘
  6. gorm软删除_gorm的简单使用和注意事项
  7. 奈飞文化手册_奈飞文化手册,如何塑造企业文化
  8. Spanning Tree Protocol介绍
  9. 自然语言处理 matlab,程序员罗杰
  10. Eclipse无提示的解决办法 和 内容辅助技巧