React15.6.0实现Modal弹层组件
注:本文Demo环境使用的是我平时开发用的配置:这里是地址。
本文适合对象
- 了解React。
- 使用过webpack3。
- 熟悉es6语法。
项目说明
项目结构截图
项目运行说明
npm install
npm run start
npm run startfe
- 登录
localhost:8088
查看demo
Modal组件分析
Modal组件是属于一个网站中比较常用的基础组件,但是在实现方面上稍微复杂一些,对场景支持的需求度较高。
这里是Antd中Modal组件的演示Demo。
首先分析这个组件的组成结构:
- title Modal弹层的标题部分。
- content Modal弹层的主体部分。
- footer Modal弹层最后的button部分。
- 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 = {};
接下来分析我们的组件都需要预留哪些接口:
- 开关状态isOpen
- Modal标题title
- Modal主体内容children
- Modal类名className
- 点击黑色区域是否可以关闭maskClosable
- 关闭按钮文案 cancelText
- 确认按钮文案 okText
- 关闭按钮回调函数 onCancel
- 确认按钮回调函数 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.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实现动画的方式有很多,但是总结起来可能只有两种:
- 使用css3实现动画。
- 根据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弹层组件
注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权
React15.6.0实现Modal弹层组件的更多相关文章
- 弹层组件-layer
layer是Layui的一个弹层组建,功能强大,总之我很喜欢,下面介绍这个组件的基本用法. 首先如果只需要使用layer而不想使用Layui可以单独下载layer组件包,页面引入jquery1.8以上 ...
- jQuery Layer 弹层组件
layer是一款近年来口碑非常不错的web弹层组件,她具备全方位的解决方案,致力于服务各个水平段的开发人员,您的页面会轻松地拥有丰富友好的操作体验. 在与同类组件的比较中,layer总是能轻易获胜.她 ...
- 基于Vue.js PC桌面端弹出框组件|vue自定义弹层组件|vue模态框
vue.js构建的轻量级PC网页端交互式弹层组件VLayer. 前段时间有分享过一个vue移动端弹窗组件,今天给大家分享一个最近开发的vue pc端弹出层组件. VLayer 一款集Alert.Dia ...
- layerweb弹层组件(SSH框架下)
action类 这里主要看业务方法中表单路径中的(isClose = "1";return resUri;) public class MaterialsAction extend ...
- 学习layer弹层组件移动版
layer弹层组件官网 常用参数: shadeClose:默认true,是否点击遮罩时关闭层
- vue3系列:vue3.0自定义全局弹层V3Layer|vue3.x pc桌面端弹窗组件
基于Vue3.0开发PC桌面端自定义对话框组件V3Layer. 前两天有分享一个vue3.0移动端弹出层组件,今天分享的是最新开发的vue3.0版pc端弹窗组件. V3Layer 一款使用vue3.0 ...
- Restful.Data v1.0 - 轻量级数据持久层组件, 正式开源发布了
经过几个星期的优化调整,今天 Restful.Data 正式开源发布. 源码地址:https://github.com/linli8/Restful 今天不写那么多废话了,还是重新介绍一下 Restf ...
- layer,备受青睐的web弹层组件
//http://layer.layui.com/ 特别说明:事件需自己绑定,以下只展现调用代码. //初体验 layer.alert('内容') //第三方扩展皮肤 layer.alert('内容' ...
- 关于微信小程序 modal弹框组件的介绍
微信小程序 modal: 这里对微信小程序中 modal组件进行详细解析,我想开发微信小程序的小伙伴可以用到,这里小编就记录下modal的知识要点. modal modal类似于javascript中 ...
随机推荐
- [国家集训队][bzoj 2152] 聪聪可可 [点分治]
题面: http://www.lydsy.com/JudgeOnline/problem.php?id=2152 思路: 题目要求统计书上路径信息,想到树上分治算法 实际上这是一道点分治裸题,我就不瞎 ...
- hdu 4096 判断路径
思路:将每个关系当成一条有向边,查询时就判断之间存在路径. #include<iostream> #include<cstdio> #include<cstring> ...
- BZOJ2395 [Balkan 2011]Timeismoney 【最小乘积生成树】
题目链接 BZOJ2395 题意:无向图中每条边有两种权值,定义一个生成树的权值为两种权值各自的和的积 求权值最小的生成树 题解 如果我们将一个生成树的权值看做坐标,那么每一个生成树就对应一个二维平面 ...
- 出租车(taxi)
出租车(taxi) 题目描述 Bessie在农场上为其他奶牛提供出租车服务.这些奶牛已经在沿着长度为M(1<= M <= 1,000,000,000)的栅栏上不同的地点聚集等候.不幸的是, ...
- bzoj3609【HEOI2014】人人尽说江南好
题意:http://www.lydsy.com/JudgeOnline/problem.php?id=3609 sol :博弈论 通过打表找规律,发现答案是%m循环的,且当m为偶数时取反 因为我太 ...
- leetcode 21 list merge
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode ...
- javaScript 笔记(5) --- jQuery(上)
这节整理整理 iquery.js 相关的内容... 目录 --- jQuery 语法 --- 文档就绪事件 --- jQuery 选择器 --- jQuery 事件 --- jQuery 效果 jQu ...
- canvas游戏开发系列(1):基础知识
canvas基础知识 canvas是什么? canvas是html5的一个元素,可以说他的功能是html元素中最强大的一个. 举个栗子: 第一步:在页面中引入canvas标签,并且设置好宽高背景等样式 ...
- 洛谷 [P1337] 平衡点
模拟退火练手 一道模拟退火的好题 结果一定势能最小 与模拟退火思路高度一致 #include <iostream> #include <cstdio> #include < ...
- poj 2699 The Maximum Number of Strong Kings 枚举 最大流
题目链接 题意 对于一个竞赛图(有向完全图),其顶点是选手,边是比赛,边\(e=(u,v)\)代表该场比赛中\(u\)战胜\(v\). 现定义选手的分数为其战胜的人的个数(即竞赛图中点的出度).并且定 ...