一、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的声明式特性和组件树的一致性。