用react封装一个 Dialog组件的填坑之旅

Dialog组件在项目中可以说是最基础的组件之一了,它在用户交互体验中起到了至关重要的作用。我们的一些信息提示都可以用Dialog组件来做,可以说一个平台的信息提示部分友好与否直接关联到了用户的体验度。

最近在用react做一个类似交易所的平台,ui稿子出来之后发现很多功能都是在弹框中完成的,于是Dialog组件的封装必不可少了。

做之前就对稿子仔细分析一番,最后得到结论就是先做一个基本的有遮罩层的Dialog组件,然后再基于这个Dialog上面封装一些Toast、Alert、Confrim组件;根据一般业务场景需求来说,最基本的Dialog组件必须有个show属性来控制显示隐藏、close按钮来点击关闭;然后根据我们的需求调整还需要有一些其他属性:如是否可以点击遮罩就触发关闭,是否显示关闭按钮等等。。。然后开始写代码:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import style from './Dialog.module.scss'
class Dialog extends Component {
constructor(props) {
    super(props)
    this.state = {
      show: this.props.show,
      showCloseButton: this.props.showCloseButton
    }
    this.maskRef = React.createRef() // 创建一个ref到时候在是否只点击遮罩时要用,配合closeOnClickMask这个属性
  }

  // 类型检查
  static propTypes = {
    show: PropTypes.bool.isRequired, // 控制显示隐藏, 必传
    padding: PropTypes.string, // padding 默认40px
    showCloseButton: PropTypes.bool, // 是否显示关闭按钮
    backgroundColor: PropTypes.string, // 背景颜色
    closeOnClickMask: PropTypes.bool, // 是否可以点击遮罩层就关闭
    onClose: PropTypes.func, // close事件
    children: PropTypes.node // 子节点
  }

// state依赖父组件props的变化而变化的话要在这里做监听
  componentWillReceiveProps(nextProps) {
    // 如果父组件的show没有变化就不理
    if (nextProps.show === this.state.show) {
      return
    }
    // 如果父组件的showCloseButton 没有变化就不理
    if (nextProps.showCloseButton === this.state.showCloseButton) {
     return
    }
  }

  $_closeHandle = e => {
    this.setState({
      show: false
    })
    typeof this.props.onClose === 'function' && this.props.onClose(e)
  }
  clickHandle = (e, type) => {
    if (type === 'close') {
      this.$_closeHandle(e)
      return
    }
    if (type === 'mask' && this.props.closeOnClickMask) {
      const target = e.target
      if (this.maskRef.current === target) {
        this.$_closeHandle(e)
      }
    }
  }

  render() {
    const wrapperStyle = {}
    this.props.padding && (wrapperStyle.padding = this.props.padding)
    this.props.backgroundColor && (wrapperStyle.backgroundColor = this.props.backgroundColor)
    return (
      <div className={style.mask}
        style={{ display: this.state.show ? 'block' : 'none' }}
        onClick={e => this.clickHandle(e, 'mask')}
        ref={this.maskRef}>
        <div className={style.wrapper} style={wrapperStyle}>
          {this.props.children} // 类似vue的slot
          <span className={style.close}
            style={{ display: this.state.showCloseButton ? 'block' : 'none' }}
            onClick={e => this.clickHandle(e, 'close')}>&times;</span>
        </div>
      </div>
    )
  }
}
export default Dialog

代码写完很快发现问题了,父组件在用的时候this.setState()时怎么设置都没用,例如:

<Dialog show showCloseButton={this.state.showCloseButton} >

</Dialog>

this.setState(preveState => ({showCloseButton: !preveState.showCloseButton}))

后来排查一看,第一个坑就来了

逻辑思维的坑: 仔细看看下面 1 有bug的代码这段代码,原意是好的,父组件的props没有变化就不操作,但是却忘了父组件的props并不止一个!!!
回到刚刚的问题:this.setState(preveState => ({showCloseButton: !preveState.showCloseButton}))不起作用就是因为这里只设置了showCloseButton,而 nextProps.show 还是等于 this.state.show,所以直接return了,根本没有检查showCloseButton的变化,导致不起作用。所以下面的逻辑应该改为 2 修改的代码:

// 1 有bug的代码
// state依赖父组件props的变化而变化的话要在这里做监听
  componentWillReceiveProps(nextProps) {
    // 如果父组件的show没有变化就不理
    if (nextProps.show === this.state.show) {
      return
    }
    // 如果父组件的showCloseButton 没有变化就不理
    if (nextProps.showCloseButton === this.state.showCloseButton) {
     return
    }
  }

//2 修改的代码:
  // state依赖父组件props的变化而变化的话要在这里做监听
  componentWillReceiveProps(nextProps) {
    if (nextProps.show !== this.state.show) {
      this.setState({
        show: nextProps.show // 监听自己的属性变化才去做操作
      })
    }
    if (nextProps.showCloseButton !== this.state.showCloseButton) {
      this.setState({
        showCloseButton: nextProps.showCloseButton
      })
    }
  }

第二个坑,showCloseButton的坑

我Dialog的showCloseButton本来是默认是true的,即是显示的,但是它也受控于父组件来控制显示和隐藏。但是父组件一般不会去设置一个showCloseButton为true,就是说父组件的需求是这样用的时候,showCloseButton还是显示的


<Dialog show></Dialog>

上面的代码原意是显示一个弹框,弹框中的关闭按钮是显示出来的
但是这样子设置弹框是出来了,showCloseButton由于没有被父组件显式的设置 为true,所以就被父组件隐式的设置为false了
除非这样子设置 按钮才显示出来:

<Dialog show showCloseButton></Dialog>

有没有办法让父组件不用特意去控制它为显示的而它又是显示的呢?查了一下文档,果然是有的,最后还要在代码中加上这段才行:

// 默认属性值
  static defaultProps = {
    showCloseButton: true // 默认是显示的,同时也受控于父组件的设置!
  }

第三个坑:父组件必须设置onClose事件来设置自己的state.show为false,看下面代码,设置弹框显示,没有设置onClose事件

// 父组件
 constructor(props) {
  super(props)
  this.state = {show: true}
}

<Dialog show={this.state.show}></Dialog>

当你点击关闭按钮时,弹框关闭了,但是你的state.show还是等于true 这时候你只要触发其他的props变化的话,Dialog组件的componentWillReceiveProps钩子就会执行

 // state依赖父组件props的变化而变化的话要在这里做监听
  componentWillReceiveProps(nextProps) {
    if (nextProps.show !== this.state.show) {
      this.setState({
        show: nextProps.show
      })
    }
    if (nextProps.showCloseButton !== this.state.showCloseButton) {
      this.setState({
        showCloseButton: nextProps.showCloseButton
      })
    }
  }

这时候,你会发现突然页面就显示弹框了.所以需要当弹框关闭时需要你把state.show设置为false ,和Dialog组件的state.show保持一致。

其实这也不算坑吧,vue中这个问题在父组件很容易解决加个.sync就行了

// vue中加修饰符sync,同时子组件设置好对应的update就好了
// 父组件
<Dialog :show.sync=show></Dialog>
//Dialog子组件
this.$emit('show:update', false)

大的问题差不多就这几个了。然后写完了我发现我们要用ant design到项目中去。。。额,沟通不到位的后果就是这组件算是白写了,呵呵。最后再多比比一句:总体来说,写react比写vue感觉啰嗦不少。现在vue3.0即将出来了,听作者的演讲,3.0的版本性能将会是一个翻倍的可能。

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

推荐阅读更多精彩内容

  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,409评论 0 17
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,116评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,723评论 2 59
  • 全媒体时代启动了信息爆炸模式, 新奇好玩的东西越来越多,很多学生无时无刻不在低头,低头不是在读书,而是在玩手...
    七荤八素的花阅读 617评论 0 0
  • 我们公司(宣传的有点明目张胆啊) 苏州道富日化有限公司创立于2005年,是一家集化妆品配方研发,包装设计,生产,销...
    daofurihua阅读 267评论 0 0