当前位置: 首页 > 工具软件 > Zent > 使用案例 >

读zent源码库之Dialog组件实现

羊柏
2023-12-01

1、Dialog组件提供什么功能,解决什么问题?

zent的Dialog组件,使用姿势是这样的(代码摘自zent官方文档:https://www.youzanyun.com/zan...

import { Dialog, Button } from 'zent';

class Example extends React.Component {
  state = { visible: false }

  triggerDialog = visible => {
    this.setState({ visible });
  };

  render() {
    return (
      <div>
        <Button
          type="primary"
          onClick={() => this.triggerDialog(true)}
        >
          显示
        </Button>
        <Dialog
                    visible={this.state.visible}
                    onClose={() => this.triggerDialog(false)}
                    title="对话框"
                >
                    <p>对话框内容</p>
                    <p>对话框其他内容</p>
                </Dialog>
      </div>
    );
  }
}

ReactDOM.render(<Example />, mountNode);
  1. 可以通过visible属性控制弹层的显示与隐藏
  2. 可以随意的在Dialog组件里添加任意多的内容
  3. 可以在任意位置使用Dialog组件

2、如果我来实现会怎么做?

如果我来实现,其实很简单,用jQuery已经写了无数遍了,但是,想想当初用jQuery来写组件的时候,非常方便的可以在body下面插入一个div,并且绑定事件,或者,直接把弹层的div写到body标签下面。

但是,react,却是组件嵌套组件,所有组件都渲染在一个被称为root的div下面,这可怎么把一个节点挂载到body下面呢?

也简单,我直接用document.body.appendChild把组件直接插入到body后面,但是发现,document.body.appendChild的参数必须是标准的dom节点,而react里面,通过this.props.children取出来的数据,并不是标准的dom节点,这也好办,把这些节点转成标准的dom节点,然后插入到body下面不就ok了?我是这么想的。但是,最后一步,事件怎么绑定呢?这块没有深入研究了,不过我想,应该这样去实现也是没有问题的。

顺便说一下,曾经我还实现过一个React的弹层,但是必须放到最外层使用。。。哈哈,不说了,都是泪,侵入太强,无法做到在任意位置使用Dialog组件

3、zent的Dialog实现方式

zent实现方式,其实跟我上面说的基本思路是一致的,只是,用的方法不再是document.body.appendChild,而是用到了一个React提供的叫做ReactDOM.unstable_renderSubtreeIntoContainer(parentComponent, element, containerNode, callback)的方法,第一个参数是当前dialog所在的父组件,第二个参数是将要渲染的dialog内容,其实就是this.props.children,第三个参数是即将要挂载到body下面的一个div容器节点,callback就是成功之后的回调了。

从函数名可以看出,这个方法是不稳定的,但是,zent还是用了,不过效果很好,哈哈。

zent的具体做法是,把ReactDOM.unstable_renderSubtreeIntoContainer方法放到了一个叫做Portal的组件上去实现这个功能,然后再把dialog内容放进这个Portal组件。

<PortalComponent
    visible={visible}
    onClose={this.onClose}
    className={`${prefix}-dialog-r-anchor`}
  >
    <DialogEl {...this.props} onClose={this.onClose} style={elStyle}>
      {this.props.children}
    </DialogEl>
</PortalComponent>

Portal组件本身的功能非常简单,仅仅只是负责把其子组件渲染到body下面的一个新创建的div下面去。其他的逻辑(比如显示、隐藏、mask之类),全部都放到dialog组件自身上去实现。当然,显示与隐藏的功能,还是由Portal来控制,隐藏的时候,Portal会把当前的子组件从body上面卸载掉。

4、极简实现

以下是一个极简的Portal实现,即把自己的子组件渲染到body上面去。

import React from 'react';
import ReactDOM from 'react-dom';

export default class Portal extends React.Component {
  renderChildren() {
    const container = document.createElement('div');
    document.body.appendChild(container);
    ReactDOM.unstable_renderSubtreeIntoContainer(this, React.Children.only(this.props.children), container);
  }
  componentDidMount() {
    this.renderChildren();
  }
  render() {
    return null;
  }
}

以下是,基于Portal的Dialog组件的极简实现:

import React from 'react';
import Portal from './portal';

export default class Dialog extends React.Component {
  render() {
    return <Portal>
      <div>{this.props.children}</div>
    </Portal>
  }
}

使用方式:

import React, { Component } from 'react';
import ReactDOM, { render } from 'react-dom';
import Dialog from './dialog';

class App extends Component {
  onTextClick = () => {
    console.log('clicked')
  }

  render() {
    return (
      <div>
        <Dialog >
          <div onClick={this.onTextClick}>this is dialog</div>
        </Dialog>
      </div>
    );
  }
}

render(<App />, document.getElementById('root'));

当然,真正要实现一个Dialog需要考虑的问题还有很多,比如控制隐藏与显示、定制动画,自定义样式,配置dialog的标题和其他属性等等,只是这些我们在jQuery时代都已经做过。

这里主要分析了Portal的实现,毕竟,在React里面,要把组件渲染到app根节点外面去并不容易。

(全文完)

 类似资料: