内容简介:React 16.0之后的改动还是很大的,除了新增加了很多新特性之外,还明确表示未来会增加async render,增加async render之后,将会在17.0的版本完全废除当前版本的三个生命周期,对于已经习惯现在写法的小伙伴来说感觉有点方(至少我有点方),所以还是提前熟悉一下,做好升级的准备吧~个人觉得升级是必然的事情,所以,还是提前准备一下,做好升级准备!我技术没有大牛的水平,所以我写文章并不是为了吸引人,一方面是记录自己新学的东西,写出来觉得自己的理解也会加深;另一方面是让比我还入门的人找到个非
React 16.0之后的改动还是很大的,除了新增加了很多新特性之外,还明确表示未来会增加async render,增加async render之后,将会在17.0的版本完全废除当前版本的三个生命周期,对于已经习惯现在写法的小伙伴来说感觉有点方(至少我有点方),所以还是提前熟悉一下,做好升级的准备吧~
个人觉得升级是必然的事情,所以,还是提前准备一下,做好升级准备!
我技术没有大牛的水平,所以我写文章并不是为了吸引人,一方面是记录自己新学的东西,写出来觉得自己的理解也会加深;另一方面是让比我还入门的人找到个非常合适的入门文章。我喜欢配上一些Demo,这样不太明白的人才能看懂,受教人群不一样,大牛可以去看官方文档说明,小白可以看看demo感受一下新特性~ Demo地址 Demo大概长这个样子:
V16.0
16.0算是一个大版本,增加了很多新特性,解决了很多痛点问题~比如,可以render字符串和数组,比如增加了Fragment,这些在使用中都有效减少了dom节点的数量;还有可以使用portals将新节点插入在任何其他非父节点的dom节点下,对于modal,message等插件是福音;还有增加了error boundary,如果使用的好你再也不会在项目里看到满屏红色或者崩溃了,哈哈~
render多类型
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元素。
React.Fragment
解决的痛点问题与上面数组是相同的,不过个人感觉更加优雅,首先不需要加上key,其次就是增加一个不渲染的空标签看起来更加的整体,因为以前已经习惯了JSX语法需要一个父标签,这种写法更符合习惯。但是在16.0里提到了Fragment,而更详细的介绍是在16.2版本里,之所以放在这里说因为和返回数组解决的痛点是类似的~ 下面例子来自官网:
// 一个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 Boundary
什么是Error Boundary?
单一组件内部错误,不应该导致整个应用报错并显示空白页,而Error Boundaries解决的就是这个问题。
在以前的React版本中,如果某一个组件内部出现异常错误,会导致整个项目崩溃直接显示空白页或者error红页,很不友好。error boundary就是解决这个问题的。
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,然后动态渲染组件,如果出现异常使用提前定义好的替换组件代替发生异常的组件,这样整个页面只有发生异常的部分被替换不影响其他内容的展示。
Portals
有些元素需要被挂载在更高层级的位置。最典型的应用场景:当父组件具有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元素并不一定必须是组件的父组件。
Portals的应用 —— Modal,message等消息提示
Portals的事件冒泡
从上图可以看出来,弹窗的父组件应该是挂载在#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。
V16.3
废弃的几个生命周期
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
这三个生命周期之中,componentWillReceiveProps平时用的频率还是特别多的,所以对于以前的项目,可能升级会是一种麻烦事,但是说是废弃,但是其实在整个V16版本,还都是可以使用的,只不过会抛出警告,而且官方会建议使用的时候加上前缀UNSAFE_。 componentWillReceiveProps ---> UNSAFE_componentWillReceiveProps
为什么要废弃这三个生命周期
React16.0之前的生命周期设计如下图:
可以看到从开始到结束,这些生命周期的设计可以捕捉到组件的每一个state和props的改变,并没有任何逻辑上的问题,而且对于我们来说写法已经形成习惯,如果废弃肯定是费力不讨好的事情。那么为啥官方还是要皮这么一下呢?
虽然我英文不好,但是还是大致看了一下,意思呢,首先就是说这三个API经常被滥用和误用,再者就是在未来版本中,要引入async render(异步渲染),而在异步渲染的场景下,这些生命周期里面的代码会在未来的React版本里存在缺陷,因此就抛弃了。 这三个API存在的问题: React v16.3 版本新生命周期函数浅析及升级方案这里讲的很清楚。
为了弥补不足新增了两个生命周期
static getDerivedStateFromProps
触发时间:在组件构建之后(虚拟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 中,一般会进行两件事,第一、判断this.props与nextProps的异同,然后更新组件state;第二、根据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节点的操作,获取的值也是更新完毕的,下面的例子也是这样的。
getSnapshotBeforeUpdate ---- 针对对dom的一些操作
触发时间: update发生的时候,在render之后,在组件dom渲染之前。
返回一个值,作为componentDidUpdate的第三个参数。 配合componentDidUpdate, 可以覆盖componentWillUpdate的所有用法。
componentWillUpdate存在的问题
- 与componentWillReceiveProps类似,同样在一层更新过程中可能会被调用多次,这样就会造成里面的回调函数可能会执行多次,浪费性能。
- 在React17引入async render之后,render阶段和commit阶段可能并不是同步连贯的,因此,componentDidUpdate和componentWillUpdate获取到的Dom可能是不同的,这样就会导致读取到的dom元素的状态是不安全的。
getSnapshotBeforeUpdate配合componentDidUpdate来保证状态的一致
getSnapshotBeforeUpdate的发生时间在render之后,组件dom渲染之前,这样可以保证此时读取的dom和componentDidUpdate的dom是一致的。
getSnapshotBeforeUpdate不是静态方法,里面可以读取this.props和this.state等信息,并且调用之后应该返回一个值作为componentDidUpdate的第三个参数
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> ); } 复制代码
V16.4
修复了getDerivedStateFromProps的bug,为了更好地兼容即将到来的异步渲染
这个点我还没太弄明白,因为我准备写的时候就已经是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, 正是为了保证与即将到来的异步渲染模式的兼容。 复制代码
React pointer events
pointer events是HTML5规范的WEB API,它主要目的是用来将鼠标(Mouse)、触摸(touch)和触控笔(pen)三种事件整合为统一的API。
如果你的应用涉及到指针的相关事件,那么这个API还是很有用的,不过这个API的兼容性不怎么样,基本主流浏览器的最新版本才支持,从React增加了这个pointer events事件来看,说明React官方还是很看重这个API的,我觉得兼容性肯定满满的会越来越好。
因为兼容性不太好,所以官方的建议是使用的时候配合第三方的polyfill来用。
React提供的pointer events
- onPointerDown
- onPointerMove
- onPointerUp
- onPointerCancel
- onGotPointerCapture
- onLostPointerCapture
- onPointerEnter
- onPointerLeave
- onPointerOver
- onPointerOut
因为平时接触较少,所以没怎么用过,就用官方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吧,按照我的理解,它最后渲染出来的效果就是官方代码那个样子。 然后看下运行效果:
OK,可以动了!等一下~官方Demo摁住和松开的时候会变颜色,这里没变颜色,打开控制台发现还是有两个报错:
嗯,原来是6个,现在变成了两个,说明还是解决了一部分问题,这是为啥呢,原来官方文档说了:它支持的事件如下,但是并不支持官方Demo里面的onGotPointerCapture和onLostPointerCapture。
原本我想提个issue来的,O(∩_∩)O哈哈~,但是发现好像有人提了,反正暂时不支持就对了。 也可能是我配置的不对?因为官方demo确实可以运行,而且浏览器版本也都支持pointer events事件,如果有大牛给我解答还是万分感谢的~
总结
升级react日后应该是必然的事情,所以提前了解一下还是有帮助的,作为使用者暂时不做深入分析,当然,我也分析不明白,单纯从更新角度来写几个demo给大家看一下变化,应该还挺清楚的~感谢阅读!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- NET Core 3.0 特性初探:C# 8、WPF、Windows Forms、EF Core
- 前后端完全分离初探
- thrift 初探
- Java反射机制初探
- WebSocket初探
- Service Worker 初探
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。