Portals (react)

Portal 提供了一种将子节点渲染到存在于父节点以外的 DOM 节点的优秀的方案。

ReactDOM.createPortal(child,container)

第一个参数 child 是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment 第二个参数是一个 DOM 元素 。

用法

通常来讲,当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点:

render(){
	// React  挂载了一个新的div ,并且把子元素渲染其中
	return(
		<div>
			{this.props.children}
		</div>	
	);
}

然而,有时候将子元素插入到 DOM 节点中的不同位置也是有好处的:

render() {
	// React 并没有 创建一个新的 div 。他只是把子元素渲染到 ‘domNode’ 中。
	// 'domNode' 是一个可以在任何位置的有效 DOM 节点。
	return ReactDOM.createPortal(
		this.props.children,
		domNode	
	);
}

一个 portal 的典型用例是当父组件有 overflow:hidden 或 z-index 样式时,但你需要子组件能够在视觉上“跳出” 其容器。例如,对话框,悬浮卡以及提示框:

注意

当在使用 portal 时,记住 管理键盘焦点就很重要。

对于模态对话框,通过遵循 WAI-ARIA 模态开发实践,来确保每个人都能够运用它。

通过 Portal 进行实践冒泡

尽管 portal 可以被放置在DOM 树中的任何地方,但在任何其他方面,其行为和普通的 React 子节点行为一致。由于 portal 任存在于 React 树,且与 DOM 树中的位置无关,那么无论其子节点是否是 portal ,像 context 这样的功能特性都是不变的。

这包含事件冒泡。一个从 portal 内部触发的事件会一直冒泡至包含 React 树的祖先,即使这些元素并不是 DOM 树中的祖先。假设存在如下 HTML 结构:

<html>
	<body>
		<div id="app-root"></div>
		<div id="model-root"></div> 
	</body>
</html>

#app-root 里的 Parent 组件能够捕获到未被捕获的兄弟节点 #modal-root 冒泡上来的事件。

//在 DOM 中有两个容器是兄弟级 (siblings)
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');

class Modal extends React.Component {
	constructor(props){
		super(props);
		this.el = document.createElement('div');
	}
	
	componentDidMount(){
		// 在 Modal 的所有子元素被挂载后,
		// 这个 portal 元素会被嵌入到 DOM 树中,
		// 这意味着子元素将被挂载到一个分离的 DOM 节点中。
		// 如果要求子组件在挂载时可以立刻接入 DOM 树,
		// 例如衡量一个DOM 节点,
		// 或者在后代节点中使用 ‘autoFocus’,
		// 则需添加 state 到 Modal 中,
		// 仅当 Modal 被插入 DOM 树中才能渲染子元素。
		modalRoot.appendChild(this.e1);
	}	
	
	componentWillUnmount(){
		modalRoot.removeChild(this.e1);
	}
	
	render(){
		return ReactDOM.createPortal(
			this.props.children,
			this.e1,
		);
	}
}

class Parent extends React.Component{
	constructor(props) {
		super(props);
		this.state = {click:0};
		this.handleClick = this.handClick.bind(this);
	}
	
	handleClick() {
		// 当子元素里的按钮被点击时,
		// 这个将会被触发更新父元素的 state,
		// 即使这个按钮在 DOM 中不是直接关联的后代
		this.setState(state => ({
			click: state.clicks + 1	
		}));	
	}
		
	render() {
		return (
			<div onClick={this.handleClick}>
				<p> Number of clicks:{this.state.clicks}</p>
				<p>
					Open up the browser DevTools
          			to observe that the button
          			is not a child of the div
          			with the onClick handler.
          		</p>
				<Modal>
					<Child />
				</Modal>
			</div>	
		);	
	}
}

function Child() {
	// 这个按钮的点击事件会冒泡到父元素
	// 因为这里没有定义 'onclick' 属性
	return (
		<div className="modal">
			<button>Click</button>
		</div>
	);
}

ReactDOM.render(<Parent />,appRoot);

在父组件里捕获一个来自 portal 冒泡上来的事件,使之能够在开发时具有不完全依赖于 portal 的更为灵活的抽象。例如,如果你在渲染一个 <Modal /> 组件,无论其是否采用 portal 实现,父组件都能够捕获其事件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值