【譯】為什麼 React16 對開發人員來說是一種福音

前端小智發表於2019-05-12

譯者:前端小智

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

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

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

錯誤處理

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;
  }
}
複製程式碼

也可以將其用作常規元件使用:

<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 屬性

【譯】為什麼 React16 對開發人員來說是一種福音

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 生命週期事件

【譯】為什麼 React16 對開發人員來說是一種福音

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

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

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

  • static getDerivedStateFromProps

  • getSnapshotBeforeUpdate

為什麼要改

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

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

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

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

####歡迎加入前端大家庭,裡面會經常分享一些技術資源。

【譯】為什麼 React16 對開發人員來說是一種福音

相關文章