React 16.0+ 新特性初探(How to use)

React 16.0以後的改動仍是很大的,除了新增長了不少新特性以外,還明確表示將來會增長async render,增長async render以後,將會在17.0的版本徹底廢除當前版本的三個生命週期,對於已經習慣如今寫法的小夥伴來講感受有點方(至少我有點方),因此仍是提早熟悉一下,作好升級的準備吧~node

我的以爲升級是必然的事情,因此,仍是提早準備一下,作好升級準備!react

我技術沒有大牛的水平,因此我寫文章並非爲了吸引人,一方面是記錄本身新學的東西,寫出來以爲本身的理解也會加深;另外一方面是讓比我還入門的人找到個很是合適的入門文章。我喜歡配上一些Demo,這樣不太明白的人才能看懂,受教人羣不同,大牛能夠去看官方文檔說明,小白能夠看看demo感覺一下新特性~ Demo地址 Demo大概長這個樣子:git

V16.0

16.0算是一個大版本,增長了不少新特性,解決了不少痛點問題~好比,能夠render字符串和數組,好比增長了Fragment,這些在使用中都有效減小了dom節點的數量;還有可使用portals將新節點插入在任何其餘非父節點的dom節點下,對於modal,message等插件是福音;還有增長了error boundary,若是使用的好你不再會在項目裏看到滿屏紅色或者崩潰了,哈哈~github

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元素。chrome

React.Fragment

解決的痛點問題與上面數組是相同的,不過我的感受更加優雅,首先不須要加上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 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 中,通常會進行兩件事,第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節點的操做,獲取的值也是更新完畢的,下面的例子也是這樣的。

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給你們看一下變化,應該還挺清楚的~感謝閱讀!

相關文章
相關標籤/搜索