用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 (
      
this.clickHandle(e, 'mask')} ref={this.maskRef}>
{this.props.children} // 类似vue的slot this.clickHandle(e, 'close')}>×
) } } export default Dialog

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





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还是显示的




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


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

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

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

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


当你点击关闭按钮时,弹框关闭了,但是你的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子组件
this.$emit('show:update', false)

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

你可能感兴趣的:(用react封装一个 Dialog组件的填坑之旅)