React 学习之 Portals 与错误边界处理

600 阅读3分钟

Portals

可以说是 插槽,但 不同于 Vue 中的 slot,它指的是将一个 React 元素渲染到指定的容器 (真实 DOM) 中

比如说,Modal 组件一般默认直接作为 body 的真实结构的子元素渲染出来,那么我们就可以借助 ReactDOM.createPortal(ReactElement, RealDOM container) 创建一个 React 元素,示例代码:

import React from 'react'
import ReactDOM from 'react-dom'
import Modal from './components/Modal'

const PortalModal = ReactDOM.createPortal(<Modal />, document.body)

export default function App() {
    return (
        <div className="app-container">
            <PortalModal />
        </div>
    )
}

我们可以在浏览器控制台中看到,真实的 Modal 组件其实是作为 body 的直接子元素渲染出来的,但通过 React 开发者工具,我们可以看到 Modal 组件在虚拟 DOM 树的结构中依旧在 App 组件下,类名为 app-container 的 div

所以,我们可以得出结论:React 组件虚拟 DOM 树结构与真实 DOM 树结构可以是不一致的

因而需要注意事件冒泡

  1. React 中的事件其实是经过包装的
  2. 它的事件冒泡是根据虚拟 DOM 树的结构来冒泡的,而不是真实 DOM 树的冒泡机制

错误边界处理

默认情况下,若一个组件在渲染期间 (render) 发生错误,那么就会导致整个组件树全部被卸载

错误边界:就是一个组件,用于捕获 渲染期间 子组件发生的错误,并有能力阻止错误继续向父组件传播

让某个组件捕获错误 (类组件):

  1. 使用静态方法 static getDerivedStateFromError,子组件渲染错误时会触发此函数

    • 静态方法,所以不能使用 this
    • 此函数返回值 (对象) 会与 state 混合覆盖状态
    • 触发时间点为:渲染子组件发生错误后,在更新页面之前
    • 只有子组件渲染发生错误,才会触发 (即自身组件发生错误或其兄弟组件、父组件发生错误均不会触发)
    import React, {PureComponent} from 'react'
    
    export default class ErrorBoundary extends PureComponent {
        state = {
            isError: false
        }
        static getDerivedStateFromError(error) {
            console.log('Rendering Error: ', error)
            return {
                isError: true
            }
        }
        render() {
            if (this.isError) {
                return (
                    <span>Something Wrong...</span>
                )
            }
            return this.props.children
        }
    }
    
  2. 使用 componentDidCatch(error, info) 函数

    • 是个实例方法
    • 运行时机在渲染子组件发生错误后,且页面更新之后 (更改状态会导致组件树卸载完之后又重新构建组件树,比较浪费效率)
    • 通常该函数用于往后台传递并记录错误信息
    import React, {PureComponent} from 'react'
    
    export default class ErrorBoundary extends PureComponent {
        state = {
            isError: false
        }
        componentDidCatch(error, info) {
            // info 即为错误的摘要信息
            console.log('Rendering Error: ', error)
            console.log('Rendering info: ', info)
            this.setState({
                isError: true
            })
        }
        render() {
            if (this.isError) {
                return (
                    <span>Something Wrong...</span>
                )
            }
            return this.props.children
        }
    }
    

注意点

某些错误,错误边界组件不会捕获

  1. 自身组件的错误

  2. 异步的错误 (如 setTimeout 中抛出的错误)

    import React, {PureComponent} from 'react'
    
    // ErrorBoundary.jsx
    export default class ErrorBoundary extends PureComponent {
        state = {
            isError: false
        }
        /* 此函数不会运行 */
        static getDerivedStateFromError(error) {
            console.log('Rendering Error: ', error)
            return {
                isError: true
            }
        }
        render() {
            if (this.isError) {
                return (
                    <span>Something Wrong...</span>
                )
            }
            return this.props.children
        }
    }
    
    // Comp.jsx Comp 组件
    export default funtion Comp() {
        setTimeout(() => {
            throw new Error('setTimeout error')
        }, 1000)
        return (
            <div>Comp</div>
        )
    }
    
    // App.jsx 使用
    export default function App() {
        return (
            <>
                <ErrorBoundary>
                    <Comp />
                </ErrorBoundary>
            </>
        )
    }
    
  3. 事件中抛出的错误

即:仅处理渲染子组件期间的同步错误