重拾React: React 16.0
前言
首先欢迎大家关注我的Github博客,也算是对我的一点鼓励,毕竟写东西没法获得变现,能坚持下去也是靠的是自己的热情和大家的鼓励,希望大家多多关注呀!从今年年初离开React开发岗,React就慢慢淡出我的学习范围。现在想重拾一下React相关的知识,可能文章所提及的知识点已经算是过时了,仅仅算作是自己的学习体验吧,
React 16.0
React 16.0发布于2017年九月,此次新版本作为一次大的版本升级,为我们许多新特性以及全新的内部架构,分别了解一下:
新的JavaScript环境支持
React依赖于ES6中的Map
与Set
类型以及requestAnimationFrame
函数(requestAnimationFrame
函数用来告知浏览器在每次动画重绘之前都调用给定的回调函数),如果你需要支持IE11以下的老版本浏览器和设备,React原生不再提供支持,必须引入polyfill。
对于Map
与Set
,我们可以在全局引入core-js
处理,对于requestAnimationFrame
而言,我们可以通过引入raf
:
import 'core-js/es6/map';
import 'core-js/es6/set';
import 'raf/polyfill';import React from 'react';
import ReactDOM from 'react-dom';ReactDOM.render(<h1>Hello, world!</h1>,document.getElementById('root')
);
新特性
组件返回
React之前的版本中,组件render
的返回值必须包含在一个根元素,因此我们经常都是将其包裹在一个div
标签中,在React16中我们直接在render
函数中返回字符串和数组。
比如存在下面的场景,假设有以下两个组件:
class Row extends Component{render() {return (<div><td>React</td><td>Vue</td><td>Angular</td></div>);}
}class Table extends Component{render() {return (<table<tr><Row /></tr></table>);}
}
在之前的版本中组件仅能返回一个根组件,Row
中的组件不得已只能用div
标签包裹,但是因为td
被div
包裹会导致浏览器无法识别,当然我们可以将tr
挪到Row
中,但是React 16.0提供了直接返回数组的形式,因此我们可以直接方便的写成:
class Row extends Component{render() {return [<th>React</th>,<th>Vue</th>,<th>Angular</th>];}
}
在组件中直接返回字符串相当于直接创建匿名文本。
异常处理处理
React 16.0 增强了异常的处理能力,在之前的React中,组件内部的错误可能会使得状态发生错乱从而导致下一次渲染发生未知的错误,然而React没有提供能优雅地捕捉这些错误并且从中恢复的方式。试想,部分程序的错误不应该干扰整个应用的流程,因而React16引入了新的概念: Error boundaries(错误边界)。
所谓的错误边界(Error boundaries )是指能够捕获子孙组件中错误,并提供打印这些错误和展示错误UI界面的组件。错误边界能够捕捉子孙组件
render
方法、生命周期以及构造函数中的错误。
举个例子:
class MyComponent extends Component {render(){throw new Error('I crashed!');return "MrErHu";}
}class ErrorBoundary extends Component {constructor(props) {super(props);this.state = { hasError: false };}componentDidCatch(error, info) {this.setState({ hasError: true });}render() {if (this.state.hasError) {return <h1>Something went wrong.</h1>;}return this.props.children;}
}export default class App extends Component {render() {return (<ErrorBoundary><MyComponent /></ErrorBoundary>);}
}
如上所示,含有componentDidCatch
的组件被称为错误边界,其功能类似于JavaScript中的catch
。值得注意是的,错误边界仅仅能够捕捉子孙组件的错误而不误捕获自身的错误。React 16.0引入了一个新的行为,任何未被捕获的错误都会卸载整个React组件树,虽然这个行为富有争议,但React开发者们认为即使什么也不显示,也比显示一堆错误更好。当然了,错误边界仅能捕捉我们上面所提到特定位置的错误,如果是事件处理中的错误,你还是得使用JavaScript的try
和catch
。
createPortal
React 16之前,并没有提供Portal的功能,如果需要渲染类似于对话框的组件则必须借助于unstable_renderSubtreeIntoContainer
与unmountComponentAtNode
,例如我们想要实现一个对话框Dialog
的组件:
class Dialog extends React.Component {render() {return null;}componentDidMount() {const doc = window.document;this.node = doc.createElement('div');doc.body.appendChild(this.node);this.renderPortal(this.props);}componentDidUpdate() {this.renderPortal(this.props);}componentWillUnmount() {unmountComponentAtNode(this.node);window.document.body.removeChild(this.node);}renderPortal(props) {unstable_renderSubtreeIntoContainer(this,<div class="dialog">{props.children}</div>,this.node);}
}
我们知道对话框是非常特殊的一种情况,不能渲染在父组件内而是需要直接渲染在body
标签下,为了解决了这个问题,在上面的代码中render
实际上并没有返回任何组件,而是在componentDidMount
生命周期中利用unstable_renderSubtreeIntoContainer
方法将对应组件直接渲染在this.node
下。需要注意的是,unstable_renderSubtreeIntoContainer
渲染的组件需要手动卸载,否则可能会造成内存泄露,因此我们在componentWillUnmount
中手动调用unmountComponentAtNode
。
有ReactDom.createPortal
,一切都变得简单的起来,既不需要手动去卸载组件,也不需要担心unstable
的API会在后续的版本中移出,上面的例子,在React 16.0可以如下实现:
class Dialog extends React.Component {constructor(props) {super(props);const doc = window.document;this.node = doc.createElement('div');doc.body.appendChild(this.node);}render() {return createPortal(<div class="dialog">{this.props.children}</div>,this.node);}componentWillUnmount() {window.document.body.removeChild(this.node);}
}
renderToNodeStream
React服务器渲染在React 16.0之前仅仅支持renderToString
,后端用字符串的方式将渲染好的HTML发送给客户端,而React 16.0则提供了renderToNodeStream
,返回一个可读流,二者有什么区别?
// using renderToString
import { renderToString } from "react-dom/server"
import App from "./App"
app.get("/", (req, res) => {res.write("<!DOCTYPE html><html><head><title>App</title></head><body>");res.write("<div id='content'>"); res.write(renderToString(<App/>));res.write("</div></body></html>");res.end();
});
// using renderToNodeStream
import { renderToNodeStream } from "react-dom/server"
import App from "./App"
app.get("/", (req, res) => {res.write("<!DOCTYPE html><html><head><title>App</title></head><body>");res.write("<div id='content'>"); const stream = renderToNodeStream(<App/>);stream.pipe(res, { end: false });stream.on('end', () => {res.write("</div></body></html>");res.end();});
});
回答这个问题之前,我们需要了解一下什么是流(Stream
),对于从事前端的同学而言,流这个概念相对比较陌生,流本质上是对输入输出设备的抽象,比如:
ls | grep *.js
ls
产生的数据通过管道符号(|
)流向了grep
命令中,数据就像水流一样在管道符号中流动。设备流向程序我们称为readable
,程序流向设备我们称为writable
,我们举一个例子:
const fs = require('fs');
const FILEPATH = './index';const rs = fs.createReadStream(FILEPATH);
const ws = fs.createWriteStream(DEST);rs.pipe(ws);
数据通过管道中从rs流向了ws,实现了复制的功能,并且数据在管道流动的过程中我们还可以对数据进行处理。那么流有哪些优点呢?首先数据不需要一次性从设备全部拿出,然后再写入另外一个设备。流可以实现一点点的放入内存中,一点点的存入设备,带来的就是内存开销的下降。并且我们可以在管道中优雅的处理数据,方便程序拓展。
讲了这么多流的优点,renderToNodeStream
为服务器渲染带来了什么呢?首先同样的道理,renderToNodeStream
可以降低渲染服务器的内存消耗,更重要的是带来TTFB
的降低。
TTFB(Time to First Byte):浏览器从最初的网络请求被发起到从服务器接收到第一个字节前所花费的毫秒数
我们知道HTTP协议在传输层使用的TCP协议,而TCP协议每次会将应用层数据切割成一个个报文传输,因此使用流不必等待所有的渲染完成才传输,可以有效降低TTFB
。
非标准DOM属性的支持
在React 16之前,React会忽视非标准DOM属性,例如:
<div mycustomattribute="something" />
在React 15中仅会输出:
<div />
在React 16中则会输出:
<div mycustomattribute="something" />
允许使用非标准DOM属性使得在集成第三方库或者尝试新的DOM API时更加的方便。
其他变化
关于setState
函数,setState(null)
将不会再触发更新,因此如果是以函数作为参数的形式调用setState
,可以通过返回null
的方式控制组件是否重新渲染,例如:
this.setState(function(state) {return null;
})
需要注意的是,与之前不同,如果在render
中直接调用setState
会触发更新,当前实际的情况是,你也不应该在render
中直接触发setState
。并且,之前的setState
的回调函数(第二个参数)是在所有组件重新渲染完之后调用,而现在会在componentDidMount
和componentDidUpdate
后立即调用。
关于生命周期中,如果一个组件从<A>
被替换成<B>
,那么React 16中B
组件的componentWillMount
一定总是先于A
组件的componentWillUnmount
,但是在React 16之前的版本某些情况下可能是相反的顺序。还有,componentDidUpdate
方法不会再接收到prevContext
的参数。
关于React Fiber
React历经两年的核心代码重构,在16.0中推出了瞩目的React Fiber
。
React最引以自豪的应该就是Virtual Dom了,Virtual Dom的运用首先使得我们前端编码的难度大大降低,所需要考虑的只有在特定状态描述UI界面,也不需要考虑浏览器该如何处理。其次,正是因为Virtual Dom的引入,使得React具备了跨平台的能力,既可以在浏览器运行(React Dom),也可以在移动端设备上运行(React Native),也就是React所宣称的:
Write once, run anywhere
顺着这个思路往下走,其实React的实现分为两个部分:
- 不同状态下不同的UI描述,React需要对比前后UI描述的差异性,明白界面到底实际发生了什么改变,这个过程在React中被称为
Reconciler
。React 16.0版本之前属于Stack Reconciler
,现在则是Fiber Reconcile
。 - 第二个则是Virtual Dom对真实环境的映射,在React Dom中是对浏览器的映射,在移动端是对特定平台(iOS、Andriod)的映射,这部分属于插件式实现,并不属于React核心代码。
正如上图所示,React运行时首先会根据返回的JSX创建对应的Element,用以描述UI界面。然后通过Element则会对应创建组件实例Instance,也就是我们所说的Virtual Dom,最后通过Virtual Dom去映射真实的浏览器环境。在首次渲染之后,后序的更新Reac只需要找到(Reconciler)两次Virtual Dom的差异性(diff
),然后通过diff
去更新真实DOM,这样就实现了增量更新真实DOM,毕竟DOM的操作是非常昂贵的。
然而之前的Stach Reconcile
相当于从最顶层的组件开始,自顶向下递归调用,不会被中断,这样就会持续占用浏览器主线程。众所周知,JavaScript是单线程运行,长时间占用主线程会阻塞其他类似于样式计算、布局绘制等运算,从而出现掉帧的情况。
Fiber Reconcile
力图解决这个问题,通过将Reconcile
进行拆分成一个个小任务,当前任务执行结束后即使还有后序任务没有执行,也会主动交还主线程的控制权,暂时将自己挂起,等到下次获得主线程的控制权时再继续执行,不仅如此,Fiber还可以对任务通过优先级进行排序,优先进行那些至关重要的操作,是不是非常类似操作系统的进程调度算法。这样做的好处就是其他类似于页面渲染的操作也能获得执行,避免因此造成卡顿。
当然至于Fiber是如何实现如此强大的功能,已经超过文章的讨论范围,目前也超过了本人的能力范围。不过,React 16带来的性能改善和一系列新特性都让我欣喜。重新使用React,看到如此多的变化,不禁想说一句:真香!
重拾React: React 16.0相关推荐
- 初探 React Router 4.0
React Router 4.0 (以下简称 RR4) 已经正式发布,它遵循React的设计理念,即万物皆组件.所以 RR4 只是一堆 提供了导航功能的组件(还有若干对象和方法),具有声明式(引入即用 ...
- Create React App 2.0 华丽登场
贺! Create React App 2.0 在 10/02 正式发布 ?????? Create React App 是由官方所维护的开发工具,主要提供了专属于 React 开发环境的前置工作.简 ...
- Create React App 2.0 正式发布:Babel 7、webpack 4 等
英文原文:Create React App 2.0: Babel 7, Sass, and More – React Blog 以下是简要翻译 React 本身是没有附带任何编译工具的,如果没有测试运 ...
- Redux 包教包会(二):趁热打铁,重拾初心
在这一部分中,我们将趁热打铁,运用上篇教程学到的 Redux 三大核心概念来将待办事项的剩下部分重构完成,它涉及到将 TodoList 和 Footer 部分的相关代码重构到 Redux,并使用 Re ...
- 重拾算法(3)——用458329个测试用例全面测试二叉树和线索二叉树的遍历算法
重拾算法(3)--用458329个测试用例全面测试二叉树和线索二叉树的遍历算法 在"上一篇"和"上上一篇"中,我给出了二叉树和线索二叉树的遍历算法.给出算法容易 ...
- php byte stringbuffer,重拾java基础(十三):String姐妹StringBuffer、StringBuilder总结
重拾java基础(十三):String姐妹StringBuffer.StringBuilder总结 一.StringBuffer类概述buffer:缓冲 2. 字符串缓冲区,跟String非常相似,都 ...
- python 移除列表中的值 list index out of range_重拾Python(二)之列表
目录: 列表切片 列表方法 列表用作栈或队列 列表推导式 叁贰壹:重拾Python(一)之字符串zhuanlan.zhihu.com 一.列表切片 python的列表切片和字符串切片是一样的 In ...
- 让你重拾信心的单片机入门,小哥带你了解
\\\插播一条: 自己在今年整理一套单片机单片机相关论文800余篇 论文制作思维导图 原理图+源代码+开题报告+正文+外文资料 想要的同学私信找我. 电子编程入门到工程师--重拾信心-单片机一篇入门 ...
- 【学习笔记】React+React全家桶学习笔记
文章目录 1 为什么要使用React 2 React的定义 3 React的三大特性 4 React入门 4.1 hello_react 4.2 虚拟DOM的创建 4.3 JSX 4.4 模块与组件, ...
- 从零开始,重拾我的个人站长之路
回想起来,还是在1999年高二时候第一次接触到网站.当时家里给买了个二手的台式机,那是个98还很时髦,yahoo还很牛逼,上网还要163拨号的年代,32M内存,1G硬盘,处理器型号现在都记不清楚了,就 ...
最新文章
- envoy实现_微服务之服务治理:Envoy 全局 gRPC 限速服务 lyft/ratelimit 详解
- App混合应用Appium自动化测试框架技术难点
- 标准差、方差、协方差的简单说明
- 学会python爬虫怎么赚钱-自学python爬虫赚钱经历
- mysql是自动表锁定吗_MySQL数据库表怎么锁定
- 第三十六期:学 Java 网络爬虫,需要哪些基础知识?
- python 判断子序列_Leetcode练习(Python):第392题:判断子序列:给定字符串 s 和 t ,判断 s 是否为 t 的子序列。...
- cookie、Session、Token、sessionStorage、localStorage简介__Token放在 cookie, sessionStorage 和 localStorage中区别
- python封装概念_Python封装及解构
- silverlight寻奇 - Graphite
- STM32工作笔记0010---认识GPIO IO端口
- c 传string 给java_JNI基础 将字符串传递给c,在c中拼接后返回给java
- 英特尔再爆重大芯片漏洞,苹果谷歌微软相继中招!
- 根据select的选项不同跳转到不同的页面
- 利用存储过程批量生成数据
- hdu 4057(ac自动机+状态压缩dp)
- 文件读写和字符串、列表的排序
- lassAtitit事件代理机制原理 基于css class的事件代理的事件代理titi
- eclipse汉化版的问题
- java删除某些段落word_Java 批量删除Word中的空白段落