Portals

在React 16.x 新增了一个名为“Protals”的特性,直接按照字面意思翻译实在不靠谱。在描述这个特性时,我们还是用官方的英文单词来指定它。Portals的作用简单的说就是为了便于开发“弹窗”、“对话框”、“浮动卡片”、“提示窗”等脱离标准文档流的组件而设定的,用于替换之前的unstable_renderSubtreeIntoContainer。

15.x之前的时代实现"弹窗"

过去没有这个特性的时候,我们使用React绘制“弹窗”之前无非就三种方法:

1.将弹窗作为一个子元素在组件中直接使用,然后赋予弹窗 {position: fixed ,z-index:99}这样的样式,让他漂浮在整个页面应用的最上层并相对与整个浏览器窗口定位。如果你认为fixed能实现所有要求,那么最好把下面的这个页面代码复制到本地运行看看:

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Fixed</title>
</head>
<body>
<div class="top-div"><div class="fixed-div">Do I look fixed to you?</div>
</div>
</body>
<style>.top-div {width: 300px;height: 300px;background: coral;transform: translate(100px, 100px);animation: diagonal-loop 1s infinite alternate;}.fixed-div {position: fixed;background: rgba(0, 0, 0, 0.7);width: 100%;height: 100%;top: 100px;left: 100px;padding: 10px;color: white;}@keyframes diagonal-loop {0% {transform: translate(100px, 100px);}100% {transform: translate(200px, 200px);}}
</style>
</html>

除此之外,这种方式处理事件的冒泡也会导致一些问题。

2.使用unstable_renderSubtreeIntoContainer方法将弹窗组件添加到body中。官方文档明确告诉你了,这玩意是有坑的,使用起来也到处是雷区。

3.最后一种方式是使用Redux来全局控制,可以在React中的模式对话框一文了解使用Redux实现对话框的内容。虽然能解决前面2个问题,但是使用 Redux 除了多引入一些包之外,这也不是一种很“自然”的实现方式。

Protals的使用

Protals组件的使用方式和普通的React组件并没有太大差异,只不过要用一个新的方法将其包裹起来:

/**
* @param child 需要展示在Protals中的组件,如<div>child</div>
* @param container 组件放置的容器,就是一个Element对象。例如 document.getElementById('pop');
*/
ReactDOM.createPortal(child, container)

通常情况下,我们需要为某个组件增加子元素都会直接写在render()方法中:

render() {return (<div>{this.props.children}</div>);
}

而如果是一个 Protals 特性的组件,我们通过下面的过程创建它:

render() {return ReactDOM.createPortal(this.props.children,domNode,);
}

Protals的事件传递

Protals特性的组件渲染成真实DOM后结构上和虚拟DOM不完全一致,但是其事件流还是像普通的React组件一样可以在父组件中接收并加以处理。所以我们依然可以按照冒泡的方式处理Protals组件的事件。

看个代码的例子,我们定义两个组件——AppPop

App是整个页面的框架,负责将Pop弹窗中输入的内容显示到页面中。React 会将弹窗直接添加为<body>的子元素。

class App extends React.Component {//constructor clickHandle() {this.setState({popShow: true})}submitHandle(value) {this.setState({message: value, popShow: false})}cancelHandle() {this.setState({popShow: false})}render() {return (<div><p>Input Message : {this.state.message}</p><button onClick={this.clickHandle}>Click</button>{this.state.popShow && <Modal><Pop onSubmit={this.submitHandle} onCancel={this.cancelHandle}/></Modal>}</div>)}
}
class Pop extends React.Component {//constructorsubmitHandle() {this.props.onSubmit(this.el.value)}render() {const {onCancel} = this.propsreturn createPortal(<div><div><span onClick={onCancel}>X</span></div><textarea ref={ref=>this.el=ref}/><div><button onClick={this.submitHandle}>submit</button><button onClick={onCancel}>cancel</button></div></div>,document.getElementById('body'))}
}

以上只是示例,已实现的源码在:https://github.com/chkui/ReactProtalExample。你可以执行下面这几步运行,并在浏览器输入http://localhost:8080/看到效果。

$ git clone https://github.com/chkui/ReactProtalExample.git
$ npm install #按照node_module
$ npm start #运行webpack

观察代码我们会发现:实现这个弹窗的效果仅仅需要在旧的React组件编码的方式上增加一层createPortal 方法包装即可。其他的处理方式没有任何变化。但是出现弹窗后,观察真实的DOM结构,你会发现弹窗是出现在<body />标签下,脱离了React的树形结构:

<body id="body"><div id="root"><div class="app"><p class="message">Input Message : Input</p><button class="button">Click</button></div></div><div class="modal"> <!-- 弹窗的DOM --><div class="mask"></div><div class="pop"><div class="title"><span class="close">X</span></div><textarea class="text" placeholder="input message"></textarea><div class="pop-bottom"><button class="button pop-btn">submit</button><button class="button pop-btn">cancel</button></div></div></div>
</body>

Error Boundaries

在 16.x 版本之前,React并没有对异常有什么处理(15.x 增加的 unstable_handleError 满地是坑),都是让使用React的开发人员按照标准JavaScript的方式自行处理可能会出现的异常,这会导致某些由底层渲染过程引起的异常很难定位。此外,由于一个React组件常常伴随多个生命周期方法(lifecycle methods),如果要全面的去处理异常,会导致代码结构越来越差。

为了解决这些坑,最新版本的React提供了一个优雅处理渲染过程异常的机制—— Error Boundaries 。同时,随着 Error Boundaries 的推出,React也调整了一些异常处理的的行为和日志输出的内容。

Error Boundaries特点

特点1:通过一个生命周期方法捕获子组件的所有异常:

/**
*@param error: 被抛出的异常
*@param info: 包含异常堆栈列表的对象
**/
componentDidCatch(error, info)

特点2:只能捕获子组件的异常,而不能捕获自身出现的异常。

特点3:只能捕获渲染方法,和生命周日方法中出现的异常。而事件方法中的异常、异步代码中的异常(例如setTimeoout、一些网络请求方法)、服务端渲染时出现的异常以及componentDidCatch方法中出现的异常是无法被捕获的。如果需要捕获这些异常,只能使用JavaScripttry/catch语法。

异常处理行为变更

16.x 之后的React的异常处理较之前有一些变动。当组件在使用的过程中出现某个异常没有被任何 componentDidCatch 方法捕获,那么 React 将会卸载掉整个 虚拟Dom树。这样的结果是任何未处理的异常都导致用户看到一个空白页面。官方的原文——“As of React 16, errors that were not caught by any error boundary will result in unmounting of the whole React component tree”。

这样的目的是尽可能保证页面完整性,避免由于页面的错误而导致业务逻辑错误。所以React升级到16.x版本后,至少在最顶层的根节点组件实现 componentDidCatch 方法并附加一个 错误提示的简单组件。如果根节点的组件需要处理的事物太复杂,最好多加一层包装组件仅处理异常。

有了 componentDidCatch 之后,我们可以更细粒度的按照模块或者业务来控制异常。还可以专门设定一个服务器接口来收集页面在客户端运行时出现的异常。

优化异常堆栈

新版本的React优化了异常输出,能够更清晰的跟踪到出错的位置。异常日志输出的内容将会比之前的React丰富很多,除了输出JavaScript的异常信息,还会清晰的定位到错误出现的组件:

如果你的项目使用最新版本的 create-react-app 创建的,那么这一项功能已经存在了。如果没使用 Create React App,那么可以通过一个 Babel 的插件添加这个功能:

$ npm install --save-dev babel-plugin-transform-react-jsx-source

然后在对应的配置(.babelrcwebpack的plugins等)中添加:

{"plugins": ["transform-react-jsx-source"]
}

切记这项功能仅仅用于开发或测试环境,切勿用于生产环境。某些浏览器可能不支持 Function.name  的属性,可能无法正确显示组件名称(例如所有版本的IE)。可以通过使用一些 polyfill 来解决这个问题,比如这个 function-name工具 。

代码实例

最后是一个代码的例子。请按照以下步骤到github上clone下来运行。

$ git clone https://github.com/chkui/ErrorBoundariesExample.git #下载代码
$ npm install #安装node_module
$ npm start #安装完后webpakc启动

例子值得关注的就几个点。

1.通过 webpack 的方式引入了babel的源码映射插件用以定位异常出现的位置。

module: {rules: [{test: /\.js$/,use: [{loader: 'babel-loader',options: {presets: ['es2015', 'stage-0', 'react'],plugins: ['transform-react-jsx-source'], //添加插件}}],exclude: /node_modules/}]},

2.定义了四个组件——AppParentChildErrorTip,分别是入口组件、父组件、子组件和捕获到异常时用来提示的组件。

class App extends React.Component {//constructorcomponentDidCatch(error, info) {this.setState({error: true}) //处理子组件的异常}render() {return (<div className="app"><h2>Example</h2>{this.state.error ? (<ErrorTip />) : (<Parent/>)}</div>)}
}
class Parent extends React.Component {//constructorclickHandle() {try {throw new Error('event error')} catch (e) {this.setState({myError: true})}}childErrorClickHandle(){this.setState({childError:true})}componentWillUpdate(nextProps, nextState) {if (nextState.myError) {throw new Error('Error')}}componentDidCatch(error, info) {this.setState({catchError: true})}render() {return (<div className="box"><p>Parent</p><button onClick={this.clickHandle}>throw parent error</button><button onClick={this.childErrorClickHandle}>throw child error</button>{this.state.catchError ? (<ErrorTip/>):(<Child error={this.state.childError}/>)}</div>)}
}
class Child extends React.Component{//constructorcomponentWillReceiveProps(nextProps){if(nextProps.error){throw new Error('child error')}}render(){return (<div className="box"><p>Child</p></div>)}
}

Child抛出的异常会被Parent组件处理、Parent组件抛出的异常会被App组件处理,组件无法捕获自生出现的异常。

最后,由于16.x版本提供了componentDidCatch的功能,所以将15.x的unstable_handleError特性取消调了,如果需要进行升级的可以去 这里 下载并使用升级工具。

React Portals与Error Boundaries相关推荐

  1. [react] 说说你对Error Boundaries的理解

    [react] 说说你对Error Boundaries的理解 错误边界是React16新推出的一种错误处理的方式,在v16之前,React的抛错会导致页面显示的错误,v16修改这种方式成了组件如果产 ...

  2. React 高级应用 -- 错误边界 Error Boundaries

    GitHub 学习 Demo. 部分 UI 的异常不应该破坏了整个应用.为了解决 React 用户的这一问题,React 16 引入了一种称为 "错误边界" 的新概念. 错误边界的 ...

  3. React入门(3)-- React的错误边界(Error Boundaries)

    熟悉React都知道,在React中一些UI的错误,比如throw new Error(),加载服务器资源报错时,或者一些js的语法错误可能会导致整个项目崩溃掉,满屏的红色报错,非常不友好.为了解决这 ...

  4. react报错Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.

    文章目录 一.报错语句是什么意思? 二.复现步骤,及其改法 1.错误代码 2.修改方式 3.报错原因 4.bind(this,data) 总结 一.报错语句是什么意思? Error: Too many ...

  5. React Portals的使用

    某些情况下,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中(默认都是挂载到id为root的DOM元 素上的). Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM ...

  6. React 报错 Error: useRoutes() may be used only in the context of a <Router> component

    使用react-router-dom V6 发生报错,找了一会才解决,做个记录 报错意思:大概就是要给路由提供一个上下文 报错原因,一开始我是把 BrowserRouter 放到 App.js 文件中 ...

  7. 错误Error: A React component suspended while rendering, but no fallback UI was specified

    解决React错误: Error: A React component suspended while rendering, but no fallback UI was specified. Add ...

  8. react和react2_为什么React16是React开发人员的福气

    react和react2 by Harsh Makadia 通过苛刻马卡迪亚 为什么React16是React开发人员的福气 (Why React16 is a blessing to React d ...

  9. React源码解毒 - render方法解析

    render方法解析 要将react元素渲染到页面当中,分为了两个阶段: render阶段 和 commit阶段. render阶段:由协调层负责的阶段 在这个阶段当中要为每一个react元素构建对应 ...

最新文章

  1. HDU 4609 3idiots
  2. FZU 1889 龟兔赛跑
  3. AB1601的波特率注意事项
  4. 超级直播sop直播源.zip_超级直播app壳 打造自己的直播app
  5. java 泛型int_Java 泛型
  6. 爱情三十一课,先信自己
  7. 查看一个定义的方法在哪些地方被使用过(vs2008)
  8. DHCP安装授权与设置分配
  9. Java面试之Synchronized无法禁止指令重排却能保证有序性
  10. mysql数据迁移到teradata_TERADATA数据库中SELECT的使用
  11. c mysql dll_PHP5.3以上版本没有libmysql.dll,以及由此带来的困扰
  12. xp系统打印机服务器win7连接不了,xp连不上win7的打印机,win7连接打印机
  13. java通过txt读取迷宫地图_java寻找迷宫路径的简单实现示例
  14. 有关XLS文件的读取
  15. image标签不显示照片img标签能显示
  16. GitLab针对关键账户接管漏洞发布安全补丁
  17. 真正厉害的人,都在延迟满足
  18. IoTeX 对话 浙江大学Bithacks:当物联网遇上区块链
  19. 【教程】情感分类识别lstm-keras版本_pos_neg_neutral三分类
  20. Python 引入上级目录

热门文章

  1. 集成运算放大电路实验报告_模电总结:第三章、集成运算放大电路
  2. 在mysql中REGEXP_在MySQL中使用RegExp中的列
  3. python构建知识库_使用Mediawiki构建个人知识库
  4. solr5 导入oracle,Solr7使用Oracle数据源导入+中文分词
  5. php cache-control,网页缓存控制 Cache-control 常见的取值有private、no-cache、max-age、must-revalidate 介绍...
  6. Java执行外部命令,并把结果回显到控制台
  7. javascript用window open的子窗口关闭自己并且刷新父窗口
  8. AppList.json文件为空,主界面清缓存后加载后还正常显示
  9. Java实训项目12:GUI学生信息管理系统 - 实现步骤 - 创建服务接口实现类
  10. 8.霍夫变换:线条——动手编码、霍夫演示_4