极简的方式实现一个Antd一样message组件

5,167 阅读2分钟

有小伙伴想造一个UI组件库的轮子,在React项目里,像message这样的需要额外插入节点的组件要怎么极(最)简(懒)的实现,又要优雅一点,显得不那么像小学生呢?

功能

  • 支持message.warn ()、message.success()方式调用
  • 页面可能同时存在多个Message
  • 定时消失

实现 MessageWrapper 组件

可能会有多个Message同时存在,因此先实现一个容器组件,用来维护一个内容列表,并且支持增加和销毁。

  • 通过 list 维护Message组件列表数据
  • Message会定时消失,onHide在消失的时候将数据清理掉
  • 实现了一个add方法给外部调用
class MessageWrapper extends React.Component {
  state = {
    list: []
  }
  add = (params = {content: '', type: 'info', key: ''}) => {
    this.setState({
      list: this.state.list.concat([params])
    })
  }
  handleHide = (msg) => {
    this.setState({
      list: this.state.list.filter(item => item != msg)
    })
  }
  render() {
    return <div className="message-wrapper">
      {
        this.state.list.map(item => <Message onHide={this.handleHide.bind(this, item)} {...item}></Message>)
      }
    </div>
  }
}

实现 Message 组件

我是用一个动画来控制组件的显示和隐藏的,监听onAnimationEnd,动画结束的时候触发onHide清理DOM。

function Message (props) {
  return <div className="message">
    <div className="message__content" onAnimationEnd={props.onHide}>
      <span className={`message__icon ${props.type}`}></span>
      {props.content}
    </div>
  </div>
}

CSS3动画来控制显示和隐藏

我是通过一个动画来控制的,在需要设置message停留时间或停留逻辑的时候不便于扩展,这里可以将动画分成出现和消失两个部分,通过setTImeout来控制。

.ani {
	animation: message 2s linear 1;
}
@keyframes message {
  0%, 100% {
    opacity: 0;
    transform: translateY(-5px);
  }
  5%, 95% {
    opacity: 1;
    transform: translateY(0);
  } 
}

将Wrapper节点插入页面并生成message对象

1、在页面里插入一个容器节点 2、通过ReactDOM.render方法将 class 组件渲染到页面,并返回组件的实例。这里只能用class组件,pure组件或hooks组件是没法通过返回实例的方式来访问内部方法的!

React官方文档不建议直接使用render函数返回的引用了,理由是未来的版本组件渲染可能变成异步。不过只要React不更新,咱还是怎么香怎么用~

3、闭包返回message对象!

export const message = (function() {
  let container = document.getElementById('message-container')
  if (!container) {
    container = document.createElement('div')
    container.setAttribute('id', 'message-container')
    document.body.appendChild(container)
  }

  const messageWrapper = ReactDOM.render(
    <MessageWrapper />,
    container
  );
  return {
    warn: (content) => {
      messageWrapper.add({
        key: getUniqueKey(),
        content,
        type: 'warn'
      })
    },
    error: (content) => {
      messageWrapper.add({
        key: getUniqueKey(),
        content,
        type: 'error'
      })
    },
    success: (content) => {
      messageWrapper.add({
        key: getUniqueKey(),
        content,
        type: 'success'
      })
    },
    
  }
})()

总结

  • 组件里实现的Wrapper里可以放各种弹层组件,需要的话,可以抽离出来。到这里,我终于知道为什么半天没搜到Antd插入节点的代码了,原来不在Antd这个包里,而是引入了一个 rc-notification 的包。
  • ReactDOM.render方法会返回class组件的实例,但建议使用calllback ref的方式异步获取。
  • 需要的话还可以补一个destroy方法,ReactDOM支持销毁组件:ReactDOM.unmountComponentAtNode(container)