React優點之一就是他的API特別簡單。通過render
方法返回一個組件的基本結構,如同一個簡單的函數,就可以得到一個可以複用的react組件。但是有時候還是會有些限制的,尤其是他的API中,不能控制組件所應該渲染到的DOM節點,這就讓一些彈層組件很難控制。當父元素設置爲overflow:hidden
的時候,問題就會出現了。
例如就像下面的這樣:
我們實際期待的效果是這樣的:
幸運的是,雖然不是很明顯,但有一個相當優雅的方式來繞過這個問題。我們學到的第一個react函數是render
方法,他的函數簽名是這樣的:
ReactComponent render(
ReactElement element,
DOMElement container,
[function callback]
)
通常情況下我們使用該方法將整個應用渲染到一個DOM節點中。好消息是該方法並不僅僅侷限於此。我們可以在一個組件中,使用ReactDom.render
方法將另一個組件渲染到一個指定的DOM
元素中。作爲一個組件的render
方法,其必須是純淨的(例如:不能改變state
或者與DOM
交互).所以我們需要在componentDidUpdate
或者 componentDidMount
中調用ReactDom.render
方法。
另外我們需要確保在父元素被卸載的時候,改組件也要被卸載掉.
整理下,我們得到下面的一個組件:
import React,{Component} from 'react';
import ReactDom from 'react-dom';
export default class RenderInBody extends Component{
constructor(p){
super();
}
componentDidMount(){//新建一個div標籤並塞進body
this.popup = document.createElement("div");
document.body.appendChild(this.popup);
this._renderLayer();
}
componentDidUpdate() {
this._renderLayer();
}
componentWillUnmount(){//在組件卸載的時候,保證彈層也被卸載掉
ReactDom.unmountComponentAtNode(this.popup);
document.body.removeChild(this.popup);
}
_renderLayer(){//將彈層渲染到body下的div標籤
ReactDom.render(this.props.children, this.popup);
}
render(){
return null;
}
}
總結下就是:
在componentDidMount
的時候手動向body內塞一個div標籤,然後使用ReactDom.render
將組件渲染到這個div標籤
當我們想把組件直接渲染到body上的時候,只需要在該組件的外面包一層RenderInBody
就可以了.
export default class Dialog extends Component{
render(){
return {
<RenderInBody>i am a dialog render to body</RenderInBody>
}
}
}
譯者增加:
將以上組件改造一下,我們就可以向指定的dom節點中渲染和卸載組件,並加上位置控制,如下:
//此組件用於在body內渲染彈層
import React,{Component} from 'react'
import ReactDom from 'react-dom';
export default class RenderInBody extends Component{
constructor(p){
super(p);
}
componentDidMount(){
/**
popupInfo={
rootDom:***,//接收彈層組件的DOM節點,如document.body
left:***,//相對位置
top:***//位置信息
}
*/
let {popupInfo} = this.props;
this.popup = document.createElement('div');
this.rootDom = popupInfo.rootDom;
this.rootDom.appendChild(this.popup);
//we can setAttribute of the div only in this way
this.popup.style.position='absolute';
this.popup.style.left=popupInfo.left+'px';
this.popup.style.top=popupInfo.top+'px';
this._renderLayer()
}
componentDidUpdate() {
this._renderLayer();
}
componentWillUnmount(){
this.rootDom.removeChild(this.popup);
}
_renderLayer(){
ReactDom.render(this.props.children, this.popup);
}
render(){
return null;
}
}
注:位置獲取和根結點判斷函數
export default (dom,classFilters)=> {
let left = dom.offsetLeft,
top = dom.offsetTop + dom.scrollTop,
current = dom.offsetParent,
rootDom = accessBodyElement(dom);//默認是body
while (current !=null ) {
left += current.offsetLeft;
top += current.offsetTop;
current = current.offsetParent;
if (current && current.matches(classFilters)) {
rootDom = current;
break;
}
}
return { left: left, top: top ,rootDom:rootDom};
}
/***
1. dom:爲響應彈層的dom節點,或者到該dom的位置後,可以做位置的微調,讓彈層位置更佳合適
*
2. classFilters:需要接收彈層組件的DOM節點的篩選類名
/