代碼地址以下:<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大師代發,拒絕轉載,轉載須要做者受權