React学习:片段(fragments) 与 插槽(Portals)

一、片段(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:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/b954960630/article/details/80200905