手把手教你封装一个Modal组件

环境搭建

create-react-app 快速搭建一个 react 的开发环境,没有用过的童鞋可参考官网

目录

  1. 新建文件 src/modal/index.jsx, 并写入一段简单的测试代码
import React, { Component } from 'react';
import './index.css';
class Modal extends Component {
  render() {
    return <div className="modal">
      这是一个modal组件
    </div>
  }
}
export default Modal;
  1. 新建文件 src/modal/index.css

  2. 修改 src/App.js, 引入 Modal 组件

import React, { Component } from 'react';
import Modal from './modal';
import './App.css';

class App extends Component {
  render() {
    return (
      <Modal / >
    );
  }
}

export default App;
  1. 在命令行输入npm start,出现如下结果,则表示环境搭建成功
modal_init.png

什么是 modal

  • 标题区
  • 内容区
  • 控制区
  • mask

modal 骨架实现

修改src/modal/index.jsx

import React from 'react';

export default class Modal extends React.Component {
    render() {
        return (
            <div>
                <div>
                    <div>标题区</div>
                    <div>内容区</div>
                    <div>
                        <button>取消</button>
                        <button>确定</button>
                    </div>
                </div>
                <div>mask</div>
            </div>
            )
    }
}

modal 样式实现

修改src/modal/index.jsx

import React from 'react';

export default class Modal extends React.Component {
    render() {
        return (
            <div className='wrapper'>
                <div className='modal'>
                    <div className='title'>标题区</div>
                    <div className='content'>内容区</div>
                    <div className='operator'>
                        <button className='close'>取消</button>
                        <button className='confirm'>确定</button>
                    </div>
                </div>
                <div className='mask'>mask</div>
            </div>
            )
    }
}

修改 src/modal/index.css

.modal {
    width: 300px;
    height: 200px;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
    z-index: 2000;
    background: #fff;
    border-radius: 2px;
    box-shadow:  inset 0 0 1px 0 #000;
}

.title {
    width: 100%;
    height: 50px;
    line-height: 50px;
    padding: 0 10px;
}

.content {
    width: 100%;
    height: 100px;
    padding: 0 10px;
}

.operator {
    width: 100%;
    height: 50px;

}

.close, .confirm {
    width: 50%;
    border: none;
    outline: none;
    color: #fff;
    background: #4CA791;
    cursor: pointer;
    
}
.close:active, .confirm:active {
    opacity: 0.6;
    transition: opacity 0.3s;
}

.mask {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: #000;
    opacity: 0.6;
    z-index: 1000;
}

效果如图所示:

modal_css.png

modal 功能开发

先思考一下 modal 组件需要实现哪些基本功能:

  • 可以通过 visible 控制 modal 的显隐
  • 标题区 和 内容区 可以自定义显示内容
  • 点击取消关闭 modal, 同时会调用名为 onClose 的回调
  • 点击确认会调用名为 onConfirm 的回调,并关闭 modal
  • 点击蒙层 mask 关闭 modal
  • animate 字段可以开启/关闭动画

控制 modal 显隐

修改 src/modal/index.jsx

import React from 'react';

import './index.css';

export default class Modal extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        // 通过父组件传递的 visible 控制显隐
        const { visible = true } = this.props
        return visible &&
        (
            <div className='wrapper'>
                <div className='modal'>
                    <div className='title'>标题区</div>
                    <div className='content'>内容区</div>
                    <div className='operator'>
                        <button className='close'>取消</button>
                        <button className='confirm'>确定</button>
                    </div>
                </div>
                <div className='mask'>mask</div>
            </div>
        )
    }
}

修改 src/App.js, 通过一个 Button 来控制 modal 的显隐

import React, { Component, Fragment } from 'react';
import Modal from './modal';
import './App.css';


class App extends Component {
    constructor(props) {
        super(props)
        this.state = {
            visible: false,
        }
    }
    showModal = () => {
            this.setState({
                visible: true,
            });
    }
  render() {
    const { visible } = this.state
    return (
        <Fragment>
            <Modal visible={visible}/ >
            <button onClick={() => this.showModal()} style={{
                'background': '#4CA791',
                'color': '#fff',
                'border': 'none',
                'width': 300,
                'height': 50,
                'fontSize': 30,
            }}>切换显隐</button>
        </Fragment>
      
    );
  }
}

export default App;

标题区和内容区可自定义

修改 src/modal/index.jsx

import React from 'react';

import './index.css';

export default class Modal extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        // 通过父组件传递的 visible 控制显隐
        const { visible = true, title, children } = this.props
        return visible &&
        (
            <div className='wrapper'>
                <div className='modal'>
                    <div className='title'>{title}</div>
                    <div className='content'>{children}</div>
                    <div className='operator'>
                        <button className='close'>取消</button>
                        <button className='confirm'>确定</button>
                    </div>
                </div>
                <div className='mask'>mask</div>
            </div>
        )
    }
}

修改 src/App.js, 从外部传入自定义的 ‘title' 和 ‘content'

import React, { Component, Fragment } from 'react';
import Modal from './modal';
import './App.css';

class App extends Component {
    constructor(props) {
        super(props)
        this.state = {
            visible: false,
        }
    }
    showModal = () => {
            this.setState({
                visible: true,
            });
    }
  render() {
    const { visible } = this.state
    return (
        <Fragment>
            <Modal
            visible={visible}
            title='这是自定义的title'>
            这是自定义的content
            </Modal>
            <button onClick={() => this.showModal()} style={{
                'background': '#4CA791',
                'color': '#fff',
                'border': 'none',
                'width': 300,
                'height': 50,
                'fontSize': 30,
            }}>切换显隐</button>
        </Fragment>
      
    );
  }
}

export default App;

控制区功能及蒙层点击功能

  • 要实现点击取消按钮关闭 modal, 那么就需要在 modal 中维护一个状态,然后用这个状态来控制 modal 的显隐,好像可行
  • 但是前面我们是通过父组件的 visible 控制 modal 的显隐,似乎矛盾
  • 要结合起来,只通过 state 来控制 modal 的显隐,props 改变只会改变 state

修改 src/modal/index.jsx

import React from 'react';

import './index.css';

export default class Modal extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            visible: false
        }
    }
    
    // 首次渲染使用父组件的状态更新 modal 中的 visible 状态,只调用一次
    componentDidMount() {
        this.setState({
            visible: this.props.visible
        })
    }

    // 每次接收 props 就根据父组件的状态更新 modal 中的 visible 状态,首次渲染不会调用
    componentWillReceiveProps(props) {
        this.setState({
            visible: props.visible
        })
    }

    handleClose = () => {
        const { onClose } = this.props
        onClose && onClose()
        this.setState({
            visible: false
        })
    }
    handleConfirm = () => {
        const { onConfirm } = this.props
        onConfirm && onConfirm()
        this.setState({
            visible: false
        })
    }
    handleMask = () => {
        this.setState({
            visible: false
        })
    }
    render() {
        // 通过父组件传递的 visible 控制显隐
        const { title, children } = this.props

        const { visible = true } = this.state

        return visible &&
        (
            <div className='wrapper'>
                <div className='modal'>
                    <div className='title'>{title}</div>
                    <div className='content'>{children}</div>
                    <div className='operator'>
                        <button className='close' onClick={this.handleClose}>取消</button>
                        <button className='confirm' onClick={this.handleConfirm}>确定</button>
                    </div>
                </div>
                <div className='mask' onClick={this.handleMask}>mask</div>
            </div>
        )
    }
}

修改 src/App.js, 从外部传入自定义的 onCloseonConfirm

import React, { Component, Fragment } from 'react';
import Modal from './modal';
import './App.css';



class App extends Component {
    constructor(props) {
        super(props)
        this.state = {
            visible: false,
        }
    }
    showModal = () => {
            this.setState({
                visible: true,
            });
    }
    onClose = () => {
        console.log('onClose');
    }
    onConfirm = () => {
        console.log('onConfirm');
    }
  render() {
    const { visible } = this.state
    return (
        <Fragment>
            <Modal
            visible={visible}
            title='这是自定义的title'
            onClose={this.onClose}
            onConfirm={this.onConfirm}
            >
            这是自定义的content
            </Modal>
            <button onClick={() => this.showModal()} style={{
                'background': '#4CA791',
                'color': '#fff',
                'border': 'none',
                'width': 300,
                'height': 50,
                'fontSize': 30,
            }}>切换显隐</button>
        </Fragment>
      
    );
  }
}

export default App;

小编是一名前端工程师,建了一个“前端内推群”,里面有BAT等大厂的HR,也有知名猎头,但大部分是前端工程师。群里会不定期发布前端相关的学习资源。群里的大佬们团队缺人,也会在群里招人。也联合一些组织不定期会举办一些活动。逢年过节会有红包。欢迎大家加入,一起成长,目前群人数较多,已达上限,可以先加机器人微信,注明“加群目的”,机器人会拉你入群。


机器人二维码.jpeg
React技术交流群.jpeg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容