内容简介: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的改变,并没有任何逻辑上的问题,而且对于我们来说写法已经形成习惯,如果废弃肯定是费力不讨好的事情。那么为啥官方还是要皮这么一下呢?
这里讲的很清楚。
为了弥补不足新增了两个生命周期
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 初探
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Building Social Web Applications
Gavin Bell / O'Reilly Media / 2009-10-1 / USD 34.99
Building a social web application that attracts and retains regular visitors, and gets them to interact, isn't easy to do. This book walks you through the tough questions you'll face if you're to crea......一起来看看 《Building Social Web Applications》 这本书的介绍吧!