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);
- 可以通过visible属性控制弹层的显示与隐藏
- 可以随意的在Dialog组件里添加任意多的内容
- 可以在任意位置使用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根节点外面去并不容易。
(全文完)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。