代码地址如下:
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.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实现动画的方式有很多,但是总结起来可能只有两种:

  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

    layer是Layui的一个弹层组建,功能强大,总之我很喜欢,下面介绍这个组件的基本用法. 首先如果只需要使用layer而不想使用Layui可以单独下载layer组件包,页面引入jquery1.8以上 ...

  2. jQuery Layer 弹层组件

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

  3. 基于Vue.js PC桌面端弹出框组件|vue自定义弹层组件|vue模态框

    vue.js构建的轻量级PC网页端交互式弹层组件VLayer. 前段时间有分享过一个vue移动端弹窗组件,今天给大家分享一个最近开发的vue pc端弹出层组件. VLayer 一款集Alert.Dia ...

  4. layerweb弹层组件(SSH框架下)

    action类 这里主要看业务方法中表单路径中的(isClose = "1";return resUri;) public class MaterialsAction extend ...

  5. 学习layer弹层组件移动版

    layer弹层组件官网 常用参数: shadeClose:默认true,是否点击遮罩时关闭层

  6. vue3系列:vue3.0自定义全局弹层V3Layer|vue3.x pc桌面端弹窗组件

    基于Vue3.0开发PC桌面端自定义对话框组件V3Layer. 前两天有分享一个vue3.0移动端弹出层组件,今天分享的是最新开发的vue3.0版pc端弹窗组件. V3Layer 一款使用vue3.0 ...

  7. Restful.Data v1.0 - 轻量级数据持久层组件, 正式开源发布了

    经过几个星期的优化调整,今天 Restful.Data 正式开源发布. 源码地址:https://github.com/linli8/Restful 今天不写那么多废话了,还是重新介绍一下 Restf ...

  8. layer,备受青睐的web弹层组件

    //http://layer.layui.com/ 特别说明:事件需自己绑定,以下只展现调用代码. //初体验 layer.alert('内容') //第三方扩展皮肤 layer.alert('内容' ...

  9. 关于微信小程序 modal弹框组件的介绍

    微信小程序 modal: 这里对微信小程序中 modal组件进行详细解析,我想开发微信小程序的小伙伴可以用到,这里小编就记录下modal的知识要点. modal modal类似于javascript中 ...

随机推荐

  1. [luogu1357] 花园 [dp+矩阵快速幂]

    题面: 传送门 思路: 把P形花圃记录为0,C形记录为1,那么一段花圃就可以状态压缩成一个整数 那么,我们可以有这样的状压dp: dp[i][S]表示前i个花圃,最后m个的状态为S的情况 如果这是一条 ...

  2. iOS-读取txt文件中文乱码

    一.情景描述: 后台给一个txt文件,编码是utf-8,在Mac电脑Xcode开发环境下读取txt文件内容,汉字会出现乱码,英文没有乱码这种情况. 二.尝试解决方法: 修改编码格式,尝试了NSUTF1 ...

  3. C++ qsort() 函数调用时实参与形参不兼容的问题解决

    <剑指OFFER>刷题笔记 —— 扑克牌顺子 LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自 ...

  4. 【12】vue-router 之路由重定向

    看之前的项目,突然发现一个不算bug的bug,之前也是一直没有想到,现在发现之后越来越觉得有必要改掉, 项目用的是vue做的,自然切换用的就是路由,一级路由包括:首页.记录和个人中心,二级路由是在记录 ...

  5. Extension Methods "点"函数方法 扩展方法

    原文发布时间为:2011-03-25 -- 来源于本人的百度文章 [由搬家工具导入] http://msdn.microsoft.com/en-us/library/bb383977.aspx 条件: ...

  6. BootLoader的一些知识

    在嵌入式操作系统中,BootLoader是在操作系统内核运行之前运行.可以初始化硬件设备.建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境.在嵌入 ...

  7. 初始化home目录

    mkdir WORKM 公共资料 科学研究 临时文档 项目开发 正在学习 专职教学 自作文档

  8. Fedora 24 Linux 环境下实现 Infinality 字体渲染增强及 Java 字体渲染改善的方法(修订)

    Fedora 24 Linux 桌面环境默认字体渲染引擎 freetype 及字体配置工具 fontconfig 采用的是未经优化的编译及设置,字体渲染效果比较差.而某些 Linux 发行版的桌面字体 ...

  9. andorid人员文件上传服务器的搭建(tomcat中)

    1.将.war文件复制到tomcat服务器webapps下,启动服务器即可 2.访问工程路径http://localhost:8080/FileUpload/index.jsp即可测试上传 3.测试成 ...

  10. Codeforces Round #450 (Div. 2) A. Find Extra One【模拟/判断是否能去掉一个点保证剩下的点在Y轴同侧】

    A. Find Extra One time limit per test 1 second memory limit per test 256 megabytes input standard in ...