React v16は、portalsと呼ばれる新機能を導入しました。 ドキュメントには次のように記載されています。
ポータルは、子を親コンポーネントのDOM階層の外部に存在するDOMノードにレンダリングするためのファーストクラスの方法を提供します。
通常、機能コンポーネントまたはクラスコンポーネントは、React要素のツリー(通常はJSXから生成されます)をレンダリングします。 React要素は、親コンポーネントのDOMがどのように見えるかを定義します。
v16より前では、レンダリングできる子タイプはごくわずかでした。
null
またfalse
(何もレンダリングしないことを意味します)。- JSX。
- 要素を反応させます。
function Example(props) {
return null;
}
function Example(props) {
return false;
}
function Example(props) {
return <p>Some JSX</p>;
}
function Example(props) {
return React.createElement(
'p',
null,
'Hand coded'
);
}
v16では、より多くの子タイプがレンダリング可能になりました。
- 数字(含む
Infinity
とNaN
). - 文字列。
- ポータルに反応します。
- レンダリング可能な子の配列。
function Example(props) {
return 42; // Becomes a text node.
}
function Example(props) {
return 'The meaning of life.'; // Becomes a text node.
}
function Example(props) {
return ReactDOM.createPortal(
// Any valid React child type
[
'A string',
<p>Some JSX</p>,
'etc'
],
props.someDomNode
);
}
Reactポータルは、ReactDOM.createPortalを呼び出すことで作成されます。 最初の引数はレンダリング可能な子である必要があります。 2番目の引数は、レンダリング可能な子がレンダリングされるDOMノードへの参照である必要があります。 ReactDOM.createPortal
React.createElementが返すものと本質的に類似したオブジェクトを返します。
ご了承ください createPortal
の中に ReactDOM
名前空間ではなく React
のような名前空間 createElement
.
一部の注意深い読者は、 ReactDOM.createPortal
署名はと同じです ReactDOM.render
、覚えやすくなります。 ただし、 ReactDOM.render
, ReactDOM.createPortal
調整プロセス中に使用されるレンダリング可能な子を返します。
いつ使用するか
Reactポータルは、親コンポーネントが持っている場合に非常に便利です overflow: hidden
宣言されているか、スタッキングコンテキストに影響するプロパティがあり、そのコンテナから視覚的に「ブレークアウト」する必要があります。 いくつかの例には、ダイアログ、グローバルメッセージ通知、ホバーカード、およびツールチップが含まれます。
ポータルを介したイベントバブリング
Reactのドキュメントはこれを非常によく説明しています。
ポータルはDOMツリーのどこにあってもかまいませんが、他のすべての点で通常のReactの子のように動作します。 ポータルはDOMツリー内の位置に関係なくReactツリーに存在するため、コンテキストなどの機能は、子がポータルであるかどうかに関係なくまったく同じように機能します。
これには、イベントのバブリングが含まれます。 ポータル内から発生したイベントは、それらの要素がDOMツリーの祖先でなくても、含まれているReactツリーの祖先に伝播します。
これにより、ダイアログやホバーカードなどのイベントを、親コンポーネントと同じDOMツリーでレンダリングされたかのように簡単に聞くことができます。
例
次の例では、Reactポータルとそのイベントバブリング機能を利用します。
マークアップは以下から始まります。
<div class="PageHolder">
</div>
<div class="DialogHolder is-empty">
<div class="Backdrop"></div>
</div>
<div class="MessageHolder">
</div>
The .PageHolder
div
私たちのアプリケーションの主要部分が存在する場所です。 The .DialogHolder
div
生成されたダイアログがレンダリングされる場所になります。 The .MessageHolder
div
生成されたメッセージがレンダリングされる場所になります。
すべてのダイアログをアプリケーションの主要部分の上に視覚的に配置する必要があるため、 .DialogHolder
div
もっている z-index: 1
宣言した。 これにより、独立した新しいスタッキングコンテキストが作成されます .PageHolder
のスタッキングコンテキスト。
すべてのメッセージをダイアログの上に視覚的に配置する必要があるため、 .MessageHolder
div
もっている z-index: 1
宣言した。 これにより、兄弟スタッキングコンテキストが作成されます。 .DialogHolder
のスタッキングコンテキスト。 でも z-index
兄弟スタッキングコンテキストのいくつかは同じ値を持っていますが、これは、次の事実のために、私たちが望むようにレンダリングします .MessageHolder
後に来ます .DialogHolder
DOMツリーで。
次のCSSは、目的のスタッキングコンテキストを確立するために必要なルールをまとめたものです。
.PageHolder {
/* Just use stacking context of parent element. */
/* A z-index: 1 would still work here. */
}
.DialogHolder {
position: fixed;
top: 0; left: 0;
right: 0; bottom: 0;
z-index: 1;
}
.MessageHolder {
position: fixed;
top: 0; left: 0;
width: 100%;
z-index: 1;
}
例には Page
にレンダリングされるコンポーネント .PageHolder
.
class Page extends React.Component { /* ... */ }
ReactDOM.render(
<Page/>,
document.querySelector('.PageHolder')
)
私たちの Page
コンポーネントは、ダイアログとメッセージをにレンダリングします .DialogHolder
そしてその .MessageHolder
、それぞれ、これらのホルダーへの参照が必要になります div
■レンダリング時。 いくつかのオプションがあります。
これらの所有者への参照を解決できます div
sレンダリングする前に Page
コンポーネントを作成し、それらをプロパティとして Page
成分。
let dialogHolder = document.querySelector('.DialogHolder');
let messageHolder = document.querySelector('.MessageHolder');
ReactDOM.render(
<Page dialogHolder={dialogHolder} messageHolder={messageHolder}/>,
document.querySelector('.PageHolder')
);
セレクターを Page
コンポーネントをプロパティとして、参照を解決します componentWillMount
最初のレンダリングと再解決のために componentWillReceiveProps
セレクターが変更された場合。
class Page extends React.Component {
constructor(props) {
super(props);
let { dialogHolder = '.DialogHolder',
messageHolder = '.MessageHolder' } = props
this.state = {
dialogHolder,
messageHolder,
}
}
componentWillMount() {
let state = this.state,
dialogHolder = state.dialogHolder,
messageHolder = state.messageHolder
this._resolvePortalRoots(dialogHolder, messageHolder);
}
componentWillReceiveProps(nextProps) {
let props = this.props,
dialogHolder = nextProps.dialogHolder,
messageHolder = nextProps.messageHolder
if (props.dialogHolder !== dialogHolder ||
props.messageHolder !== messageHolder
) {
this._resolvePortalRoots(dialogHolder, messageHolder);
}
}
_resolvePortalRoots(dialogHolder, messageHolder) {
if (typeof dialogHolder === 'string') {
dialogHolder = document.querySelector(dialogHolder)
}
if (typeof messageHolder === 'string') {
messageHolder = document.querySelector(messageHolder)
}
this.setState({
dialogHolder,
messageHolder,
})
}
}
ポータルのDOM参照があることを確認したので、ダイアログとメッセージを使用してページコンポーネントをレンダリングできます。
React要素と同様に、Reactポータルはコンポーネントのプロパティと状態に基づいてレンダリングされます。 この例では、2つのボタンがあります。 1つは、クリックしたときにダイアログホルダーに表示されるダイアログポータルを作成し、もう1つは、メッセージホルダーに表示されるメッセージポータルを作成します。 これらのポータルへの参照は、renderメソッドで使用されるコンポーネントの状態のままにします。
class Page extends React.Component {
// ...
constructor(props) {
super(props);
let { dialogHolder = '.DialogHolder',
messageHolder = '.MessageHolder' } = props
this.state = {
dialogHolder,
dialogs: [],
messageHolder,
messages: [],
}
}
render() {
let state = this.state,
dialogs = state.dialogs,
messages = state.messages
return (
<div className="Page">
<button onClick={evt => this.addNewDialog()}>
Add Dialog
</button>
<button onClick={evt => this.addNewMessage()}>
Add Message
</button>
{dialogs}
{messages}
</div>
)
}
addNewDialog() {
let dialog = ReactDOM.createPortal((
<div className="Dialog">
...
</div>
),
this.state.dialogHolder
)
this.setState({
dialogs: this.state.dialogs.concat(dialog),
})
}
addNewMessage() {
let message = ReactDOM.createPortal((
<div className="Message">
...
</div>
),
this.state.messageHolder
)
this.setState({
messages: this.state.messages.concat(message),
})
}
// ...
}
イベントがReactポータルコンポーネントから親コンポーネントにバブルすることを示すために、クリックハンドラーを追加しましょう。 .Page
div
.
class Page extends React.Component {
// ...
render() {
let state = this.state,
dialogs = state.dialogs,
messages = state.messages
return (
<div className="Page" onClick={evt => this.onPageClick(evt)}>
...
</div>
)
}
onPageClick(evt) {
console.log(`${evt.target.className} was clicked!`);
}
// ...
}
ダイアログまたはメッセージをクリックすると、 onPageClick
イベントハンドラーが呼び出されます(別のハンドラーが伝播を停止しなかった場合)。
上記のデモンストレーションの動作例を参照してください。
👉遭遇したときにReactポータルを使用する overflow: hidden
またはコンテキストの問題を積み重ねます!