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以前的生命週期設計以下圖:
能夠看到從開始到結束,這些生命週期的設計能夠捕捉到組件的每個state和props的改變,並無任何邏輯上的問題,並且對於咱們來講寫法已經造成習慣,若是廢棄確定是費力不討好的事情。那麼爲啥官方仍是要皮這麼一下呢? 雖然我英文很差,可是仍是大體看了一下,意思呢,首先就是說這三個API常常被濫用和誤用,再者就是在將來版本中,要引入async render(異步渲染),而在異步渲染的場景下,這些生命週期裏面的代碼會在將來的React版本里存在缺陷,所以就拋棄了。 這三個API存在的問題: React v16.3 版本新生命週期函數淺析及升級方案這裏講的很清楚。觸發時間:在組件構建以後(虛擬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}
/>
複製代碼
OK,能夠動了!等一下~官方Demo摁住和鬆開的時候會變顏色,這裏沒變顏色,打開控制檯發現仍是有兩個報錯: 嗯,原來是6個,如今變成了兩個,說明仍是解決了一部分問題,這是爲啥呢,原來官方文檔說了:它支持的事件以下,可是並不支持官方Demo裏面的onGotPointerCapture和onLostPointerCapture。這個包就是一種polyfill吧,按照個人理解,它最後渲染出來的效果就是官方代碼那個樣子。 而後看下運行效果:
本來我想提個issue來的,O(∩_∩)O哈哈~,可是發現好像有人提了,反正暫時不支持就對了。也多是我配置的不對?由於官方demo確實能夠運行,並且瀏覽器版本也都支持pointer events事件,若是有大牛給我解答仍是萬分感謝的~
升級react往後應該是必然的事情,因此提早了解一下仍是有幫助的,做爲使用者暫時不作深刻分析,固然,我也分析不明白,單純從更新角度來寫幾個demo給你們看一下變化,應該還挺清楚的~感謝閱讀!