一、片段(fragments)
片段(fragments) 可以让你将子元素列表添加到一个分组中,并且不会在DOM中增加额外节点
。
1、片段长什么样?
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
2、为什么会用片段?
看下面例子:
class Columns extends React.Component {
render() {
return (
<div>
<td>Hello</td>
<td>World</td>
</div>
);
}
}
class Table extends React.Component {
render() {
return (
<table>
<tr>
<Columns />
</tr>
</table>
);
}
}
为了渲染有效的 HTML , <Columns />
需要返回多个<td>
元素。如果 <Columns />
的 render() 函数里面使用一个父级 div ,那么最终生成的 HTML 将是无效的,像下面这样:
在 <Table />
组件中的输出结果:
<table>
<tr>
<div><!--增加了多余的div标签,且让html无效-->
<td>Hello</td>
<td>World</td>
</div>
</tr>
</table>
所以,我们介绍 Fragment。
3、使用片段
我们使用 片段 改写上面例子。
class Columns extends React.Component {
render() {
return (
<React.Fragment>
<td>Hello</td>
<td>World</td>
</React.Fragment>
);
}
}
class Table extends React.Component {
render() {
return (
<table>
<tr>
<Columns />
</tr>
</table>
);
}
}
在正确的 <Table />
组件中,这个结果输出如下:
<table>
<tr>
<td>Hello</td>
<td>World</td>
</tr>
</table>
4、简写语法
有一个新的,更短的语法可以用来声明 片段(fragments) 。 它看起来像空标签:
class Columns extends React.Component {
render() {
return (
<>
<td>Hello</td>
<td>World</td>
</>
);
}
}
您可以像使用其他元素一样使用<></>
,只是它不支持 键(keys) 或 属性(attributes)。
请注意, 目前许多工具都不支持这个简写语法 , 所以你可能需要明确地使用 <React.Fragment>
,直到工具支持这个语法。
5、带 key 的 片段(fragments)
如果你需要一个带 key 的片段,你可以直接使用 <React.Fragment />
。 一个使用场景是映射一个集合为一个片段数组 — 例如:创建一个描述列表:
function Glossary(props) {
return (
<dl>
{props.items.map(item => (
// 没有`key`,将会触发一个key警告
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
);
}
key 是唯一可以传递给 Fragment 的属性。在将来,我们可能增加额外的属性支持,比如事件处理。
二、插槽(Portals)
插槽(Portals)能将子节点渲染到父组件的 DOM 层次之外。
1、怎么用?
ReactDOM.createPortal(child, container)
第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 片段(fragment)。第二个参数(container)则是一个 DOM 元素。
例:
render() {
// React *不* 会创建一个新的 div。 它把 children 渲染到 `domNode` 中。
// `domNode` 可以是任何有效的 DOM 节点,不管它在 DOM 中的位置。
return ReactDOM.createPortal(
this.props.children,
domNode,
);
}
对于 portal 的一个典型用例是当父组件有 overflow: hidden 或 z-index 样式,但你需要子组件能够在视觉上 “跳出(break out)” 其容器。
例如,对话框、hovercards以及提示框。
2、应用
例:效果类似于 点我点我
HTML:
<body>
<div id="app-root"></div>
<div id="mask-root"></div>
</body>
CSS:
#modal-root {position: relative;z-index: 999;}
.modal {
color:#fff;background-color: rgba(0,0,0,1);
position: fixed;top: 0;left: 0;
height: 100%;width: 100%;display: flex;
align-items: center;justify-content: center;
}
React
const appRoot = document.getElementById('app-root');
const maskRoot = document.getElementById('mask-root');
class Mask extends React.Component{
constructor(props){
super(props);
this.el = document.createElement('div');
}
componentDidMount(){
maskRoot.appendChild(this.el);
}
componentWillUnmount(){
maskRoot.removeChild(this.el);
}
render(){
return ReactDOM.createPortal(
this.props.children,
this.el
);
}
}
class App extends React.Component{
constructor(props){
super(props);
this.state = {
showMask:false
}
this.handler = this.handler.bind(this);
}
handler(){
let showMask = this.state.showMask;
this.setState({showMask:!showMask});
}
render(){
let showMask = this.state.showMask;
let mask = showMask ?
<Mask>
<div className='modal'>
adadsadadad
<button onClick={this.handler}>hidden</button>
</div>
</Mask>
: null;
return (
<div>
This div has overflow: hidden.
<button onClick={this.handler}>show</button>
{mask}
</div>
);
}
}
ReactDOM.render(<App/>,appRoot);
执行结果:
点击”show”按钮后,弹出覆盖物
3、通过 Portals 进行事件冒泡
尽管 portal 可以被放置在 DOM 树的任何地方,但在其他方面其行为和普通的 React 子节点行为一致。如上下文特性依然能够如之前一样正确地工作,无论其子节点是否是 portal,由于 portal 仍存在于 React tree 中,而不用考虑其在 DOM tree 中的位置。
这包含事件冒泡。一个从 portal 内部会触发的事件会一直冒泡至包含 React tree 的祖先。
例:效果类似于 点我点我
HTML:
<div id="app-root"></div>
<div id="mask-root"></div>
CSS:
#modal-root {position: relative;z-index: 999;}
.modal {
color:#fff;background-color: rgba(0,0,0,0.5);
position: fixed;top: 0;left: 0;
height: 100%;width: 100%;display: flex;
align-items: center;justify-content: center;
}
React:
const appRoot = document.getElementById('app-root');
const maskRoot = document.getElementById('mask-root');
class Mask extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
maskRoot.appendChild(this.el);
}
componentWillUnmount() {
maskRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el,
);
}
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {clicks: 0};
}
handleClick = () => {
this.setState({
clicks: this.state.clicks + 1
});
}
render() {
return (
<div onClick={this.handleClick}>
<p>Number of clicks: {this.state.clicks}</p>
<Mask>
<Child />
</Mask>
</div>
);
}
}
function Child() {
return (
<div className="modal">Click me</div>
);
}
ReactDOM.render(<Parent />, appRoot);
执行结果:
每点击一下,number加1: