【譯】爲何 React16 對開發人員來講是一種福音

譯者:前端小智html

原文:medium.freecodecamp.org/why-react16…前端

就像人們對更新移動應用程序和操做系統感到興奮同樣,開發人員也應該對更新框架感到興奮。不一樣框架的新版本具備新特性和開箱即用的技巧。react

下面是將現有應用程序從 React 15 遷移到 React 16 時應該考慮的一些好特性。git

想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!github

錯誤處理

React 16 引入了錯誤邊界的新概念。編程

如今在React 16中,你們就能使用錯誤邊界功能,而不用一發生錯誤就解除整個程序掛載了。把錯誤邊界當作是一種相似於編程中try-catch語句的機制,只不過是由 React 組件來實現的。數組

錯誤邊界是一種React組件。它及其子組件造成一個樹型結構,能捕獲JavaScript中全部位置的錯誤,記錄下錯誤,而且還能顯示一個後備界面,避免讓用戶直接看到組件樹的崩潰信息。安全

這裏涉及到一種新的生命週期函數叫componentDidCatch(error, info)。不管什麼樣的類組件,只要定義了這個函數,就成爲了一個錯誤邊界。框架

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // 你還能夠將錯誤記錄到錯誤報告服務中
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // 能夠渲染任何自定義回退界面
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}
複製代碼

也能夠將其用做常規組件使用:less

<ErrorBoundary>  
   <MyWidget />
</ErrorBoundary>
複製代碼

componentDidCatch() 方法的工做原理相似於JavaScript catch{}塊,但它適用於組件。只有類組件能夠是錯誤邊界。實際上,在大多數狀況下,你都但願聲明一次錯誤邊界組件,而後在整個應用程序中使用它。

請注意,錯誤邊界只會捕獲位於它們之下的組件中的錯誤。錯誤邊界沒法捕獲到自身的錯誤。若是錯誤邊界渲染錯誤消息失敗,錯誤將被傳播到上方最接近的錯誤邊界。這也相似於 JavaScript 中的 catch{}塊。

有了錯誤邊界,即便某個組件的結果有錯誤,整個React程序掛載也不會被解除。只有出錯的那個組件會顯示一個後備界面,而整個程序仍然徹底正常運行。

點擊查看在線事例

關於錯誤邊界更多的內容可查看官網

新的 render 返回類型:片斷和字符串

如今,在渲染時能夠擺脫將組件包裝在 div 中。

你如今能夠從組件的 render 方法返回元素數組。與其餘數組同樣,你須要爲每一個元素添加一個鍵以免發出鍵警告:

render() {
  // No need to wrap list items in an extra element!
  return [
    // Don't forget the keys :)
    <li key="A">First item</li>,
    <li key="B">Second item</li>,
    <li key="C">Third item</li>,
  ];
}
複製代碼

從React 16.2.0開始,它支持JSX的一個特殊片斷語法,該語法不須要鍵。

render() {
  return (
    <>
      <ChildA />
      <ChildB />
      <ChildC />
    </>
  );
}
複製代碼

支持返回字符串:

render() {
  return 'Look ma, no spans!';
}
複製代碼

Portal

Portal 提供了一種將子節點渲染到父節點以外的 dom 節點。

ReactDOM.createPortal(child, container)
複製代碼

第一個參數 (child)是任何可渲染的 React子元素,例如元素,字符串或片斷。 第二個參數 (container) 是 DOM 元素。

如何使用它

在 React15.X 版本中,咱們只能講子節點在父節點中渲染,基本用法以下:

render() {
  // React須要建立一個新的div來包含子節點
  return (
    <div>
      {this.props.children}
    </div>
  );
}
複製代碼

可是若是須要將子節點插入到父節點以外的dom呢,React15.x 及以前都沒有提供這個功能的 API。 可使用 React16.0 中的 portal:

render() {
  // React不須要建立一個新的div去包含子元素,直接將子元素渲染到另外一個
  //dom節點中
  //這個dom節點能夠是任何有效的dom節點,不管其所處於dom樹中的哪一個位置 

  return ReactDOM.createPortal(
    this.props.children,
    domNode,
  );
}
複製代碼

Portal 的一個典型用例是這樣的:當父組件帶有 overflow:hiddenz-index 樣式時,你但願子組件在視覺上可以「突破」它的容器。例如,對話框、懸停卡和工具提示。

點擊查看在線事例

自定義 DOM 屬性

React15 會忽略任何未知的 DOM 屬性。React 會跳過它們,由於沒法識別它們。

// 你的代碼
<div mycustomattribute="something" />
複製代碼

React 15 將渲染一個空的 div:

// React 15 output:
<div />
複製代碼

在 React16 中,輸出將以下所示(會顯示自定義屬性,而且徹底不會被忽略)

// React 16 output:
<div mycustomattribute="something" />
複製代碼

在 state 中設置 null 避免從新渲染

有時候咱們須要經過函數來判斷組件狀態更新是否觸發從新渲染,在 React 16 中,咱們能夠經過調用 setState 時傳入 null 來避免組件從新渲染,這也就意味着,咱們能夠在 setState 方法內部決定咱們的狀態是否須要更新,

const MAX_PIZZAS = 20;

function addAnotherPizza(state, props) {
  // Stop updates and re-renders if I've had enough pizzas.
  if (state.pizza === MAX_PIZZAS) {
    return null;
  }

  // If not, keep the pizzas coming! :D
  return {
    pizza: state.pizza + 1,
  }
}

this.setState(addAnotherPizza);
複製代碼

更多相關信息請閱讀這裏

建立 ref

如今使用 React16 建立refs要容易得多。 爲何須要使用refs

  • 管理焦點、文本選擇或媒體播放。

  • 觸發動畫。

  • 與第三方 DOM 庫集成。

ref 是使用 React.createRef() 建立的,並經過 ref 屬性附加到 React 元素。ref 一般是在構造組件時被分配給實例的屬性,以便在整個組件中引用它們。

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }

  render () {
    return <div ref={this.myRef}></div>
  }
}
複製代碼

訪問 ref

上述是建立Ref指向的方法, 在Ref 所指向的組件,在render後就能夠調用,React16.3 中提供了current 屬性,用於引用render後的節點:

componentDidMount(){
  console.log(this.myRef.current);
  //render以後就能夠輸出該ref指向的那個節點
}
複製代碼

此外,一樣的 Ref 所指向的節點能夠是 dom 節點,也能夠是類組件。

Ref 的值因節點的類型不一樣而有所不一樣:

  • 當 ref 屬性用於 HTML 元素時,在構造函數中使用 React.createRef() 建立的 ref 將底層 DOM 元素做爲 current 屬性。

  • 當 ref 屬性用於自定義類組件時,ref 對象將已掛載的組件實例做爲 current 屬性。

  • 你可能不會在函數組件上使用 ref 屬性,由於它們沒有實例。

Context API

Context 提供了一種經過組件樹傳遞數據的方法,無需在每一層手動傳遞 prop

React.createContext

const { Provider, Consumer } = React.createContext(defaultValue)
複製代碼

建立{Provider,Consumer}對。當 React 渲染 Consumer 時,它將從樹中最接近的 Provider 讀取當前上下文值。

defaultValue 參數只在消費者在樹中找不到匹配的 Provider 時纔會用到,這在單獨測試組件時十分有用。注意:將 undefined 做爲 Provider 值傳遞進去並不會致使 Consumer 使用 defaultValue

Provider

<Provider value={/* some value */}>
複製代碼

一個容許 Consumer 訂閱上下文變動的 React 組件。

一個 Provider 能夠鏈接多個 Consumer,能夠在樹中嵌套 Provider,實現更深的值覆蓋。

Consumer

<Consumer>
  {value => /* render something based on the context value */}
</Consumer>
複製代碼

訂閱上下文更改的 React 組件。

須要一個函數做爲子組件。這個函數接收當前上下文值,並返回一個 React 節點。傳給函數的 value 參數將等於樹中最近的 Providervalue。若是沒有匹配的 Provider,則 value 參數將等於傳給 createContext() 的 defaultValue。

static getDerivedStateFromProps()

在很長一段時間內,componentWillReceiveProps是在沒有附加渲染的狀況下更新狀態的惟一方法。

在版本16.3中,咱們引入了一個全新的生命週期函數getDerivedStateFromProps用來替換componentWillReceiveProps,並用更安全的方式處理相同的場景。

與此同時,咱們意識到人們對如何使用這兩種方法有不少誤解,咱們發現了一些反模式,這些錯誤致使了微妙而使人困惑的bug。

在16.4中,有關getDerivedStateFromProps的修復使得派生狀態更加可預測,所以錯誤使用的結果更容易被注意到。

getDerivedStateFromProps 會在調用 render 方法以前被調用,它應該返回一個用於更新狀態的對象,或者若是不更新任何狀態就返回 null

這個方法適用於一些罕見的用例,其中 state 依賴 prop 的變化。例如,能夠很方便地實現一個<Transition> 組件,它會比較上一個和下一個子組件,而後決定它們中的哪一個須要進行動畫渲染。

衍生 state 會致使冗長的代碼,並讓你的組件難以開發和維護。

你能夠考慮更簡單的替代方案:

  • 若是你須要在 prop 發生變動時作一些其餘事情(例如數據提取或動畫),請改用 componentDidUpdate 生命週期。

  • 若是你只想在 prop 發生變動時從新計算某些數據,請改用 memoization helper

* 若是你想在 prop 發生變動時「重置」某個狀態,請考慮建立受控組件或帶有鍵的非受控組件。

  • 此方法無權訪問組件實例。 若是你願意,能夠經過提取組件props的純函數和類定義以外的狀態,在getDerivedStateFromProps() 和其餘類方法之間重用一些代碼。

注意,無論怎樣,這個方法都會在每次進行渲染時觸發。這與 UNSAFE_componentWillReceiveProps 徹底相反。它只在父組件進行從新渲染時觸發,並且不做爲本地 setState 的結果。

nextProps.someValue與this.props.someValue進行比較。 若是二者都不一樣,那麼咱們執行一些操做:

static getDerivedStateFromProps(nextProps, prevState){   
  if(nextProps.someValue!==prevState.someValue){     
    return { someState: nextProps.someValue};  
   }
   return null
}
複製代碼

它接收兩個參數 nextPropsprevState。如前所述,你沒法在這個方法中訪問 this。你必須將 prop 存儲在 state 中,而後將 nextProps 與以前的 prop 進行對比。在上面的代碼中,nextProps 和 prevState 進行了比較。若是二者不一樣,則返回一個用於更新狀態的對象,不然就返回 null,表示不須要更新狀態。若是 state 發生變動,就會調用 componentDidUpdate,咱們能夠像在 componentWillReceiveProps 中那樣執行所需的操做。

React 生命週期事件

react v16.3,最大的變更莫過於生命週期去掉了如下三個:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

同時爲了彌補失去上面三個週期的不足又加了兩個:

  • static getDerivedStateFromProps

  • getSnapshotBeforeUpdate

爲何要改

舊的生命週期十分完整,基本能夠捕捉到組件更新的每個state/props/ref,沒有什從邏輯上的毛病。

可是架不住官方本身搞事情,react打算在17版本推出新的Async Rendering,提出一種可被打斷的生命週期,而能夠被打斷的階段正是實際dom掛載以前的虛擬dom構建階段,也就是要被去掉的三個生命週期。

生命週期一旦被打斷,下次恢復的時候又會再跑一次以前的生命週期,所以componentWillMount,componentWillReceiveProps, componentWillUpdate 都不能保證只在掛載/拿到props/狀態變化的時候刷新一次了,因此這三個方法被標記爲不安全。

你的點贊是我持續分享好東西的動力,歡迎點贊!

交流

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

github.com/qq449245884…

我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!

關注公衆號,後臺回覆福利,便可看到福利,你懂的。

相關文章
相關標籤/搜索