React学习笔记[13]✨~了解Portals、Fragment、StrictMode👻

137 阅读4分钟

我正在参加「掘金·启航计划」

一、Portals

某些情况下,我们希望渲染的内容独立于父组件,甚至独立于当前挂在的DOM元素(默认都是挂载到id为root的DOM元素上的)

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

ReactDOM.createPortal(child, container)

第一个参数:是任何可渲染的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 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框

案例

比如将h2挂在到id为zs的节点下

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <div id="zs"></div>
  </body>
</html>

App.jsx

import React, { PureComponent } from 'react'
import { createPortal } from "react-dom"

export class App extends PureComponent {
  render() {
    return (
      <div className='app'>
        <h1>App H1</h1>
        {
          createPortal(<h2>App H2</h2>, document.querySelector("#zs"))
        }
      </div>
    )
  }
}

export default App

使用场景

比如,开发一个Modal组件,它可以将它的子组件渲染到屏幕的中间位置

  1. 为index.html添加新的节点(用于挂在Modal组件)
  2. 为挂载节点添加样式
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
    <style>
      #modal {
        position: fixed;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
      }
    </style>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <div id="modal"></div>
  </body>
</html>
  1. 编写组件代码并将组件挂在至新节点上

Modal.jsx

import { PureComponent } from 'react'
import { createPortal } from "react-dom"

export class Modal extends PureComponent {
  render() {
    return createPortal(this.props.children, document.querySelector("#modal"))
  }
}

export default Modal

App.jsx

import React, { PureComponent } from 'react'
import { createPortal } from "react-dom"
import Modal from './Modal'

export class App extends PureComponent {
  render() {
    return (
      <div className='app'>
        {/* Modal组件 */}
        <Modal>
          <h2>我是标题</h2>
          <p>我是内容, 哈哈哈</p>
        </Modal>
      </div>
    )
  }
}

export default App

二、Fragment

在之前开发中,总是要在一个组件返回的内容外包裹一个div元素,如果我们又要保持内容外包裹一个根元素,但又不希望渲染出来一个div,怎么操作呢?

此时要使用Fragment

  • Fragment允许你将子列表进行分组,而无需向DOM添加额外节点
  • React中还提供了Fragment的短语法: <></>
  • 需要注意的是,如果想在Fragment上添加key,就不能使用短语法<></>

基本使用方法

import React, { PureComponent, Fragment } from 'react'

export class App extends PureComponent {
  constructor() {
    super() 

    this.state = {
      sections: [
        { title: "1", content: "我是内容1" },
        { title: "2", content: "我是内容2" },
        { title: "3", content: "我是内容3" },
        { title: "4", content: "我是内容4" },
      ]
    }
  }

  render() {
    const { sections } = this.state

    return (
      <>
        <h2>我是App的标题</h2>
        <p>我是App的内容, 哈哈哈哈</p>
        <hr />

        {
          sections.map(item => {
            return (
              // 添加key,必须使用Fragment
              <Fragment key={item.title}>
                <h2>{item.title}</h2>
                <p>{item.content}</p>
              </Fragment>
            )
          })
        }
      </>
    )
  }
}

export default App

三、StrictMode

StrictMode 是一个用来突出显示应用程序中潜在问题的工具:

  • 与 Fragment 一样,StrictMode 不会渲染任何可见的 UI
  • 它为其后代元素触发额外的检查和警告
  • 严格模式检查仅在开发模式下运行、它们不会影响生产构建;

严格模式检查的是什么?

  1. 识别不安全的生命周期
  2. 校验是否使用过时的ref API

比如,是否通过this.refs.xx的方式来使用ref

如何在React18中正确使用ref,详见文章React18中如何正确使用ref

  1. 检查意外的副作用

这个组件的constructor会被调用两次

这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用,在生产环境中,是不会被调用两次的

  1. 是否使用废弃的findDOMNode方法

在之前的React API中,可以通过findDOMNode来获取DOM,不过已经不推荐使用了

  1. 检测是否使用过时的context API

早期的Context是通过static属性声明Context对象属性,通过getChildContext返回Context对象等方式来使用Context的

Home.jsx

import React, { PureComponent } from 'react'

export class Home extends PureComponent {
  constructor(props) {
    super(props)

    console.log("Home Constructor")
  }

  componentDidMount() {
    console.log("Home componentDidMount")
  }

  render() {
    console.log("Home Render")

    return (
      <div>
        <h2>Home</h2>
      </div>
    )
  }
}

export default Home

App.jsx

import React, { PureComponent, StrictMode } from 'react'
import Home from './pages/Home'
import Profile from './pages/Profile'

export class App extends PureComponent {
  render() {
    return (
      <div>
        <StrictMode>
          <Home/>
        </StrictMode>
      </div>
    )
  }
}

export default App