React將組件渲染到指定DOM節點

React優點之一就是他的API特別簡單。通過render 方法返回一個組件的基本結構,如同一個簡單的函數,就可以得到一個可以複用的react組件。但是有時候還是會有些限制的,尤其是他的API中,不能控制組件所應該渲染到的DOM節點,這就讓一些彈層組件很難控制。當父元素設置爲overflow:hidden 的時候,問題就會出現了。

例如就像下面的這樣:
imgae

我們實際期待的效果是這樣的:
image

幸運的是,雖然不是很明顯,但有一個相當優雅的方式來繞過這個問題。我們學到的第一個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節點的篩選類名
/

原文地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章