React 16 的 Fragment 和 Portals 详解

发布于:2025-08-10 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、Fragment(片段)

**作用:**在不添加额外DOM节点的情况下组合子元素,解决组件必须返回单个根元素的限制。
核心价值:

// 传统方式:需要额外的div包裹
return (
	<div>
		<ChildA />
		<ChildB />
	</div>
);

// 使用Fragment:避免多余节点
return (
	<React.Fragment>
		<ChildA />
		<ChildB />
	</React.Fragment>
);

三种使用方式:

  • 1、显示语法:
<React.Fragment>
  <td>单元格1</td>
  <td>单元格2</td>
</React.Fragment>
  • 2、短语法(推荐):
<>
  <td>单元格1</td>
  <td>单元格2</td>
</>
  • 3、带key的Fragment(用于循环):
{items.map(item => (
  <React.Fragment key={item.id}>
    <dt>{item.name}</dt>
    <dd>{item.value}</dd>
  </React.Fragment>
))}

实际应用场景:

场景 传统方案问题 Fragment解决方案
表格结构 额外div破坏表格语义 直接包裹tr/td
CSS布局 多余节点影响布局 保持DOM结构纯净
组件结合 不必要的嵌套层级 扁平化组件结构

二、Portals(传送门)

作用: 将子节点渲染到父组件DOM层次结构之外的DOM节点中。
核心API:

ReactDOM.createPortal(child, containerDOMNode);

典型使用场景:

// 创建Modal组件
class Modal extends React.Component {
	constructor(props) {
		super(props);
		this.el = document.createElement('div');
	}

	componentDidMount() {
		// 挂载到body下的专用容器
		document.getElementById('modal-root').appendChild(this.el);
	}
	
	componentWillUnmount() {
    	document.getElementById('modal-root').removeChild(this.el);
  	}
	
	render() {
		return ReactDOM.createPortal(
			this.props.children,
			this.el
		);
	}
}

// 使用示例
function App() {
	return (
		<div>
			<MainContent />
			<Modal>
				<div className = 'modal'>模态内容</div>
			</Modal>
		</div>
	);
}

关键特性:
1、DOM位置自由:

<!-- 常规渲染位置 -->
<div id = 'root'>...</div>

<!-- Portal渲染位置 -->
<div id = 'modal-root'></div>

2、事件冒泡机制:

// 即使Modal渲染在body下,点击事件仍会冒泡到React组件树
<div onClick = { handleClick }> {/* 能捕获Modal内的点击事件 */}
	<Modal>
		<button>Click</button>
	</Modal>
</div>

3、上下文保留:

// Modal内部组件仍能访问App的Context
<AppContext.Provider value = { data }>
	<Modal>{/* 能访问data */}</Modal>
</AppContext.Provider>  

使用场景对比:

场景 传统方案问题 Protal解决方案
模态框 z-index层级问题 直接挂在到body下
全局提示 被父容器样式覆盖 脱离父容器限制
工具提示 被overflow: hidden裁剪 避免布局约束
微前端集成 需要嵌入外部系统 挂载到外部DOM节点

三、组合使用示例

function Tooltip({ content, targetId }) {
	const targetEl = document.getElementById(targetId);

	return ReactDOM.createProtal(
		<div className = 'tooltip'>
			<>{/* Fragment 包裹多个元素 */}
				<span className = 'arrow' />
				{ content }
			</>
		</div>,
		document.body
	);
}

四、版本注意事项

特性 React 15 及之前 React 16 +
Fragment 需第三方库实现 原生支持
Portals 无官方方案 ReactDOM.createPortal
短语法 不支持 <>…</>

五、最佳实践

1、Fragment 使用建议

  • 优先使用短语法<>
  • 循环中必须指定key
  • 避免在Fragment上添加属性(短语法不支持)

2、Portal 使用建议

// 正确:动态管理容器
componentDidMount() {
  portalRoot.appendChild(this.el);
}
componentWillUnmount() {
  portalRoot.removeChild(this.el);
}

// 错误:直接使用document.body
render() {
	// 可能导致多次添加 / 删除
	return createPortal(children, document.body);
}
  • 配合CSS实现层叠上下文:
#modal-root {
	position: relative;
	z-index: 999; /* 确保高于主内容 */ 
}

六、总结对比

特性 Fragment Portals
主要目的 避免多余DOM包裹 渲染到DOM树不同位置
DOM 影响 不创建新节点 在指定容器创建节点
事件处理 正常冒泡 事件仍冒泡到React组件树
典型场景 列表/表格组合元素 模态框/提示框/全局组件
语法要求 支持短语法<> 必须用React DOM.createPortal

Fragment 和 Portals 共同解决了React 应用中的DOM结构限制问题,使开发者能更灵活地控制渲染输出,同时保持React的声明式特性和组件树的一致性。


网站公告

今日签到

点亮在社区的每一天
去签到