這幾天專案要處理非常麻煩的畫面,子層被父層 overflow: hidden 加 postion relative 切到部分展開元件,突然想到之前同事提到新功能 Portals,Portals 能夠將元件向任意 dom 節點依附渲染,非常酷炫的功能,想說就順便來研究一下 React Portals。
這邊會介紹兩個範例,頁面上渲染元件,還有渲染元件到 window open。
React Portals rule
要用 React Portals 必須遵照規定的格式 ReactDOM.createPortal(element,container),第一個參數要傳遞需要渲染的物件,第二個參數 container,建立document.createElement('div')
作為 render 的 container。
ReactDOM.createPortal(element, container);
會需要用到兩個 lifeCycle,第一個是 componentDidMount react 元件 render 後調用,另一個則是 componentWillUnmount 在元件被移除後會調用。
再來在 componentDidMount 上加入要渲染元素的節點,讓這個 component render 後調用 appendChild,讓 dom 依附渲染上去。如果要移除渲染的話,則是控制 component 移除觸發 componentWillUnmount,再調用 removeChild 就完成了移除。
// create component
class Portal extends React.Component {
constructor(props) {
super(props);
// create element as render container element
this.el = document.createElement("div");
}
componentDidMount() {
// select element to render
const elementId = document.getElementById("elementId");
elementId.appendChild(this.el);
}
componentWillUnmount() {
// select element to remove
const elementId = document.getElementById("elementId");
elementId.removeChild(this.el);
}
render() {
// first argument as element or string or fragment
// second argument as render container
return ReactDOM.createPortal(this.props.children, this.el);
}
}
Portals example
聽起來可能有點抽象,實際來寫個範例看看。點擊左邊的按鈕是使用 Portals 顯示圖片,右邊的則是一般父子層顯示方法。
會發現左邊用 Portals 顯示,image 元件會打印到 section 外,避免右邊的一般父子渲染方法,在 section 內被 overflow hidden 蓋住。
Portals open window example
Portals 也可以打印到 window open 頁面的節點上。這邊範例是使用了 open window,再將元件透過 document.body.appendChild 渲染到開啟的視窗上。這邊是看到 medium 的範例,是利用 setInterval 每秒執行一次 setState,處理 小視窗 更新 parent 的視窗,有試驗過 postMessage 沒效果。
class Portal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement("div");
this.Window = null;
}
componentDidMount() {
this.Window = window.open("", "", "width=800,height=500");
this.Window.document.body.appendChild(this.el);
}
componentWillUnmount() {
this.Window.close();
}
render() {
return ReactDOM.createPortal(this.props.children, this.el);
}
}