React 16.0以后的改动仍是很大的,除了新增长了不少新特性以外,还明确表示将来会增长async render,增长async render以后,将会在17.0的版本彻底废除当前版本的三个生命周期,对于已经习惯如今写法的小伙伴来讲感受有点方(至少我有点方),因此仍是提早熟悉一下,作好升级的准备吧~node
我的以为升级是必然的事情,因此,仍是提早准备一下,作好升级准备!react
我技术没有大牛的水平,因此我写文章并非为了吸引人,一方面是记录本身新学的东西,写出来以为本身的理解也会加深;另外一方面是让比我还入门的人找到个很是合适的入门文章。我喜欢配上一些Demo,这样不太明白的人才能看懂,受教人群不同,大牛能够去看官方文档说明,小白能够看看demo感觉一下新特性~ Demo地址 Demo大概长这个样子:git
16.0算是一个大版本,增长了不少新特性,解决了不少痛点问题~好比,能够render字符串和数组,好比增长了Fragment,这些在使用中都有效减小了dom节点的数量;还有可使用portals将新节点插入在任何其余非父节点的dom节点下,对于modal,message等插件是福音;还有增长了error boundary,若是使用的好你不再会在项目里看到满屏红色或者崩溃了,哈哈~github
16.0之后,react的render函数增长了几种类型,包括字符串和数组类型。算法
render() {
//不须要再把全部的元素绑定到一个单独的元素中了
return [
// 别忘记加上key值
<li key="A"/>First itemli>,
<li key="B"/>Second itemli>,
<li key="C"/>Third itemli>,
];
}
// 也能够用下面这种写法
// 不须要再把全部的元素绑定到一个单独的元素中了
render() {
const arr = ['Adams', 'Bill', 'Charlie'];
const Arr = () => (arr.map((item, index) => <p key={index}>{item}</p>));
return <Arr />
}
复制代码
从上图能够看出,解决了以往必须在外层包一个父元素div的限制,有效的减小了没必要要的dom元素。chrome
解决的痛点问题与上面数组是相同的,不过我的感受更加优雅,首先不须要加上key,其次就是增长一个不渲染的空标签看起来更加的总体,由于之前已经习惯了JSX语法须要一个父标签,这种写法更符合习惯。可是在16.0里提到了Fragment,而更详细的介绍是在16.2版本里,之因此放在这里说由于和返回数组解决的痛点是相似的~ 下面例子来自官网:express
// 一个Table组件,里面嵌套了columns组件
class Table extends React.Component {
render() {
return (
<table>
<tr>
<Columns />
</tr>
</table>
);
}
}
// columns组件
class Columns extends React.Component {
render() {
return (
<div>
<td>Hello</td>
<td>World</td>
</div>
);
}
}
复制代码
上面设计符合react,组件式划分,可是最后渲染出来却不是最佳的,由于columns的最外层嵌套了一层没用的div标签。这个问题存在于16.0以前。
有了Fragment之后,很好的解决问题:数组
import React, { Fragment } from 'react';
class Columns extends React.Component {
render() {
return (
<Fragment>
<td>Hello</td>
<td>World</td>
</Fragment>
);
}
}
// Fragment的语法糖
<>
<td>Hello</td>
<td>World</td>
</>
两个空标签
复制代码
这块糖有点苦,官方明明说的是语法糖,可是我试了,编译通不过,而且官方也特地说明了可能使用该语法糖会出现问题,可是给出的解决办法我都试了,仍是不成功,可能配置的不对吧,有谁配置好了能够留言告诉我一下,不过无伤大雅,我却是以为语法糖也不必定必须使用。浏览器
单一组件内部错误,不该该致使整个应用报错并显示空白页,而Error Boundaries解决的就是这个问题。安全
在之前的React版本中,若是某一个组件内部出现异常错误,会致使整个项目崩溃直接显示空白页或者error红页,很不友好。error boundary就是解决这个问题的。
按照个人我的理解,error boundary本质上就是一个组件,只不过组件内部多出现了一个生命周期,componentDidCatch,在这个生命周期里面,它会捕捉本组件下的全部子组件抛出的异常错误,包括堆栈信息。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// 使用起来就跟普通组件同样
<ErrorBoundary>
<ChildCompA />
<ChildCompB />
...
</ErrorBoundary>
复制代码
上面代码是官网给出的例子,在ErrorBoundary组件内,定义state={ hasError: false },在componentDidCatch内部捕捉到error,而后动态渲染组件,若是出现异常使用提早定义好的替换组件代替发生异常的组件,这样整个页面只有发生异常的部分被替换不影响其余内容的展现。
有些元素须要被挂载在更高层级的位置。最典型的应用场景:当父组件具备overflow: hidden或者z-index的样式设置时,组件有可能被其余元素遮挡,这个时候你就能够考虑要不要使用Portal使组件的挂载脱离父组件。
Portals 提供了一种很好的将子节点渲染到父组件之外的 DOM 节点的方式。
render() {
// React does *not* create a new div. It renders the children into `domNode`.
// `domNode` is any valid DOM node, regardless of its location in the DOM.
return ReactDOM.createPortal(
this.props.children,
domNode,
);
}
复制代码
通常而言,组件在装载的时候会就近装载在该组件最近的父元素下,而如今你可使用Portal将组件渲染到任意一个已存在的dom元素下,这个dom元素并不必定必须是组件的父组件。
从上图能够看出来,弹窗的父组件应该是挂载在#app这个dom下面的,经过portals,咱们将modal框挂载在#portal_modal这个dom下了。虽然最后的modal组件没有挂载在整个应用所在的#app下,可是portals建立的组件里面的事件依然会冒泡给它自身的父组件,父组件能够捕获到被挂载在#portal_modal节点下面的modal的点击事件。
class PortalsComp extends Component {
constructor(props) {
super(props);
this.state = { showModal: false, clickTime: 0 };
}
handleShow = () => {
this.setState({ showModal: true });
}
handleHide = () => {
this.setState({ showModal: false });
}
handleClick = () => {
let { clickTime } = this.state;
clickTime += 1;
this.setState({ clickTime });
}
render() {
const protalModal = this.state.showModal ? (
<PortalModal>
<ModalContent hideModal={this.handleHide} />
</PortalModal>
) : null;
return (
<div className={s.portalContainer} onClick={this.handleClick}>
<div>该组件被点击了: {this.state.clickTime}次</div>
<Button onClick={this.handleShow} type='primary'>点我弹出Modal</Button>
{protalModal}
</div>
);
}
}
export default PortalsComp;
复制代码
从上图能够看出来,portals的组件虽然挂载在其余dom下,可是父组件依然能够捕获到modal的冒泡事件,打开和关闭,父组件显示点击次数为2。
这三个生命周期之中,componentWillReceiveProps平时用的频率仍是特别多的,因此对于之前的项目,可能升级会是一种麻烦事,可是说是废弃,可是其实在整个V16版本,还都是可使用的,只不过会抛出警告,并且官方会建议使用的时候加上前缀UNSAFE_。 componentWillReceiveProps ---> UNSAFE_componentWillReceiveProps
React16.0以前的生命周期设计以下图:
触发时间:在组件构建以后(虚拟dom以后,实际dom挂载以前) ,以及每次获取新的props以后。
每次接收新的props以后都会返回一个对象做为新的state,返回null则说明不须要更新state. 配合componentDidUpdate,能够覆盖componentWillReceiveProps的全部用法。
// before
componentWillReceiveProps(nextProps) {
if (nextProps.flag !== this.props.flag) {
this.setState({ flag: nextProps.flag }, () => {
if (nextProps.flag) {
this.doSmething();
}
});
}
}
// 在16.3以后的版本使用,react推荐下面这种写法,不然eslint可能会提示警告
UNSAFE_componentWillReceiveProps(nextProps) {
// your code
}
// after
static getDrivedStateFromProps(nextProps, prevState) {
if (nextProps.flag !== prevState.flag) {
// 更新state
return {
flag: nextProps.flag
}
}
// 不更新state
return null;
}
// state更新事后须要作的事放在componentDidUpdate里
componentDidUpdate(prevProps, prevState) {
if (prevState.flag !== this.props.flag) {
this.doSomething();
}
}
复制代码
写法与以前相比要麻烦了一些,可是处理逻辑上应该是更清晰了。在 componentWillReceiveProps 中,通常会进行两件事,第1、判断this.props与nextProps的异同,而后更新组件state;第2、根据state的变化更新组件或者执行一些回调函数。在之前的写法里,这两件事咱们都须要在 componentWillReceiveProps 中去作。而在新版本中,官方将两件事分配到了两个不一样的生命周期 getDerivedStateFromProps 与 componentDidUpdate 中去作,使得组件总体的更新逻辑更为清晰,getDerivedStateFromProps里面进行state的更新,componentDidUpdate里作更新以后的各类回调。并且在 getDerivedStateFromProps 中还禁止了组件去访问 this.props(static方法,获取不到组件的this),强制让开发者去比较 nextProps 与 prevState 中的值,以确保当开发者用到 getDerivedStateFromProps 这个生命周期函数时,就是在根据当前的 props 来更新组件的 state,而不是去作其余一些让组件自身状态变得更加不可预测的事情。
仍是须要适应,虽然习惯了之前的写法,可是如今这种性能要更好。并且毕竟之后会废弃。
这里解决了个人好久一个困惑,组件最后的更新过程实际上是: componentWillReceiveProps(static getDerivedStateFromProps)判断state的变化 ---> shouldComponentUpdate判断是否进行更新 render阶段会根据diff算法来生成须要更新的虚拟dom结构 ---> 更新虚拟dom ---> 虚拟dom更新完毕马上调用componentDidUpdate ---> 最后完成渲染。
由于官方给出的定义是,componentDidUpdate是在组件dom更新结束以后当即调用,那么这个更新结束我理解的就是dom已经更新完毕渲染好了,可是我在componentDidUpdate里面调用了alert,发现其实进入该生命周期以后,其实dom还未发生变化,可是页面上的dom未发生变化,而componentDidUpdate获取dom的时候值确实正确的,可能这里是虚拟dom和真实dom不一样步的关系吧,总之就是,在componentDidUpdate里面能够获取dom节点的操做,获取的值也是更新完毕的,下面的例子也是这样的。
触发时间: update发生的时候,在render以后,在组件dom渲染以前。
返回一个值,做为componentDidUpdate的第三个参数。 配合componentDidUpdate, 能够覆盖componentWillUpdate的全部用法。
getSnapshotBeforeUpdate的发生时间在render以后,组件dom渲染以前,这样能够保证此时读取的dom和componentDidUpdate的dom是一致的。
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.disabled !== prevState.disabled) {
return {
disabled: nextProps.disabled
};
}
return null;
}
getSnapshotBeforeUpdate(prevProps, prevState) {
return this.props.disabled;
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (!snapshot) {
// 若是snapshot是false,获取焦点
this.domRef.focus();
}
}
render() {
return (
<div>
<input ref={(ref) => this.domRef = ref} disabled={this.state.disabled} />
</div>
);
}
复制代码
这个点我还没太弄明白,由于我准备写的时候就已是16.4了,也不太知道这个bug会致使什么影响,不过看了一些文章,大概意思是下面这样: 参考文章:React16.4 新特性
React此次更新修复了getDerivedStateFromProps这个生命周期的触发节点, 在以前, 它触发的方式和旧生命周期getDerivedStateFromProps相似, 都是在被父组件re-render的时候才会触发,而且本组件的setState的调用也不会触发
这种方式在以前同步渲染的时候是没有问题的, 可是为了支持新的还未启用的fiber异步渲染机制, 如今, getDerivedStateFromProps在组件每一次render的时候都会触发,也就是说不管是来自父组件的re-render, 仍是组件自身的setState, 都会触发getDerivedStateFromProps这个生命周期。
要理解为何react修复了这个生命周期的触发方式, 咱们首先得了解react的异步渲染机制
react异步渲染
要理解react异步渲染的机制, 咱们首先要说一说react以前是如何进行渲染。
在react16以前, 组件的渲染都是同步进行的, 也就是说从constructor开始到componentDidUpdate结束, react的运行都是没有中断的, 生命周期开始以后就会运行到其结束为止, 这样带来的一个缺点就是,若是组件嵌套很深, 渲染时间增加了以后, 一些重要的, 高优先级的操做就会被阻塞, 例如用户的输入等, 这样就会形成体验上的不友好。
在以后即将到来的异步渲染机制中, 会容许首先解决高优先级的运行,同时会暂停当前的渲染进程,当高优先级的进程结束以后, 再返回继续运行当前进程, 这样会大大的提升react的流畅度,给用户带来更好的体验
而此次修复getDerivedStateFromProps, 正是为了保证与即将到来的异步渲染模式的兼容。
复制代码
pointer events是HTML5规范的WEB API,它主要目的是用来将鼠标(Mouse)、触摸(touch)和触控笔(pen)三种事件整合为统一的API。
若是你的应用涉及到指针的相关事件,那么这个API仍是颇有用的,不过这个API的兼容性不怎么样,基本主流浏览器的最新版本才支持,从React增长了这个pointer events事件来看,说明React官方仍是很看重这个API的,我以为兼容性确定满满的会愈来愈好。
由于兼容性不太好,因此官方的建议是使用的时候配合第三方的polyfill来用。
由于平时接触较少,因此没怎么用过,就用官方Demo给你们看看吧,必定要升级到14以上哦,不然没有这些属性,感兴趣的深刻研究研究,毕竟这篇文章目的就是让本身了解一下新特性~
官方demo效果以下:
【坑来了】:我自信满满的升级到Firefox和chrome到最新版本,而后把官方demo跑了一下,可是WTF?是下面这样的结果。。。
react-pointable
,
// 首先,安装包
yarn add react-pointable
// 而后代码变成下面
import Pointable from 'react-pointable';
...
<Pointable
style={circleStyle}
onPointerDown={this.onDown}
onPointerMove={this.onMove}
onPointerUp={this.onUp}
onPointerCancel={this.onUp}
onGotPointerCapture={this.onGotCapture}
onLostPointerCapture={this.onLostCapture}
/>
复制代码
这个包就是一种polyfill吧,按照个人理解,它最后渲染出来的效果就是官方代码那个样子。 而后看下运行效果:
本来我想提个issue来的,O(∩_∩)O哈哈~,可是发现好像有人提了,反正暂时不支持就对了。也多是我配置的不对?由于官方demo确实能够运行,并且浏览器版本也都支持pointer events事件,若是有大牛给我解答仍是万分感谢的~
升级react往后应该是必然的事情,因此提早了解一下仍是有帮助的,做为使用者暂时不作深刻分析,固然,我也分析不明白,单纯从更新角度来写几个demo给你们看一下变化,应该还挺清楚的~感谢阅读!