React15.6.0實現Modal彈層組件

代碼地址以下:<br>http://www.demodashi.com/demo/12315.htmlcss

注:本文Demo環境使用的是我平時開發用的配置:這裏是地址html

本文適合對象

  1. 瞭解React。
  2. 使用過webpack3。
  3. 熟悉es6語法。

項目說明

項目結構截圖

項目運行說明

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

Modal組件分析

Modal組件是屬於一個網站中比較經常使用的基礎組件,可是在實現方面上稍微複雜一些,對場景支持的需求度較高。node

這裏是Antd中Modal組件的演示Demoreact

首先分析這個組件的組成結構:webpack

  1. title Modal彈層的標題部分。
  2. content Modal彈層的主體部分。
  3. footer Modal彈層最後的button部分。
  4. background 整個黑色背景

其次,這個彈層不能生硬的出現,因此必定要有動畫效果。css3

最後,彈層是在合適的地方經過用戶交互的形式出現的,因此又一個控制器來控制Modal彈層的出現和關閉。git

Modal組件的實現

靜態組件

首先來思考如何實現靜態組件部分的代碼。es6

先在components下面建立咱們的modal組件結構。github

  • -components/
  • -modal/
    • -modal.js
    • -modal.scss

這裏樣式文件使用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 = {};

接下來分析咱們的組件都須要預留哪些接口:

  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彈層組件

代碼地址以下:<br>http://www.demodashi.com/demo/12315.html

注:本文著做權歸做者,由demo大師代發,拒絕轉載,轉載須要做者受權

相關文章
相關標籤/搜索