React 特性剪輯(版本 16.0 ~ 16.9)

牧云云發表於2019-03-04

React 特性剪輯(版本 16.0 ~ 16.9)

Before you're going to hate it, then you're going to love it.

React 特性剪輯(版本 16.0 ~ 16.9)

Concurrent Render(貫穿 16)

在 18年的 JSConf Iceland 上, Dan 神提到 Concurrent Render 涉及到 CPU 以及 IO 這兩方面。

React 特性剪輯(版本 16.0 ~ 16.9)

Time Slicing 對應解決左側的問題, Suspense 對應解決了右側的問題。它們共同要解決的是的提升使用者體驗, 在更多的場景下都可以做到可互動。而 Fiber 架構是上述兩者的基石。

Time Slicing

在 16 之前的版本的渲染過程可以想象成一次性潛水 30 米,在這期間做不了其它事情(Stack Reconciler);

React 特性剪輯(版本 16.0 ~ 16.9)

痛點概括:

  • 一次性渲染到底
  • 中途遇到優先順序更高的事件無法調整相應的順序

接著拿上面的潛水例子為例,現在變為可以每次潛 10 米,分 3 個 chunk 進行; chunk 和 chunk 之間通過連結串列連線; chunk 間插入優先順序更高的任務, 先前的任務被拋棄。

React 特性剪輯(版本 16.0 ~ 16.9)

開啟 Fiber 後,獲取非同步資料的方法應放到 render 後面的生命週期鉤子裡(phase 2 階段)進行, 因為 render 前面的生命週期鉤子(phase 1階段)會被執行多次

注意: 並沒有縮短原先元件的渲染時間(甚至還加長了),但使用者卻能感覺操作變流暢了。

requestIdleCallback(): 借力此 api, 瀏覽器能在空閒的時間處理低優先順序的事。

Suspense(16.6, 16.8, 16.9)

Suspense 意思是能暫停當前元件的渲染, 當完成某件事以後再繼續渲染。

  • code splitting(16.6, 已上線): 檔案懶載入。在此之前的實現方式是 react-loadable
  • 併發模式(16.8, 2019 年 Q2 季度): 在檔案懶載入的同時能做其它互動;
  • data fetching(16.9 版本, 2019 年中): 資料動態呈現;
import React, { lazy, Suspense } from 'react'
const OtherComponent = lazy(() => import('./OtherComponent'))

function MyComponent() {
  return (
    <Suspense fallback={<div>loading...</div>}>
      <OtherComponent />
    </Suspense>
  )
}
複製程式碼

一種簡單的預載入思路, 可參考 preload

const OtherComponentPromise = import('./OtherComponent');
const OtherComponent = React.lazy(() => OtherComponentPromise);
複製程式碼

render 新增的返回型別

在 React16 版本中 render() 增加了一些返回型別,到目前為止支援的返回型別如下:

  • React elements.
  • Arrays and fragments.
  • Portals.
  • String and numbers.
  • Booleans or null.

render

其中 render() 支援返回 Arrays 能讓我們少寫一個父節點, 如下所示:

const renderArray = () => [
  <div key="A">A</div>
  <div key="B">B</div>
]
複製程式碼

render() 支援返回陣列的特性類似 Fragments(16.2), 使用 Fragments 可以不用寫 key。

Portals(傳送門)

將 React 子節點渲染到指定的節點上

案例:實現一個 Modal 元件,demo

另外關於 Portals 做到冒泡到父節點的兄弟節點這個現象, demo, 我想可以這樣子實現:如果元件返回是 Portal 物件,則將該元件的父元件的上的事件 copy 到該元件上。其實並不是真的冒泡到了父節點的兄弟節點上。

Error Boundaries

React 16 提供了一個新的錯誤捕獲鉤子 componentDidCatch(error, errorInfo), 它能將子元件生命週期裡所丟擲的錯誤捕獲, 防止頁面全域性崩潰。demo

componentDidCatch 並不會捕獲以下幾種錯誤

  • 事件機制丟擲的錯誤(事件裡的錯誤並不會影響渲染)
  • Error Boundaries 自身丟擲的錯誤
  • 非同步產生的錯誤
  • 服務端渲染

服務端渲染

服務端渲染一般是作為最後的優化手段, 這裡淺顯(缺乏經驗)談下 React 16 在其上的優化。

在 React 16 版本中引入了 React.hydrate(), 它的作用主要是將相關的事件注水html 頁面中, 同時會比較前端生成的 html 和服務端傳到前端的 html 的文字內容的差異, 如果兩者不一致將前端產生的文字內容替換服務端生成的(忽略屬性)。

支援自定義屬性

在 React 16 版本中, 支援自定義屬性(推薦 data-xxx), 因而 React 可以少維護一份 attribute 白名單, 這也是 React 16 體積減少的一個重要因素。

Context(16.3、16.6)

Context 相當於是用元件化的方式使用 global, 使用其可以共享認證的使用者、首選語言(國際化)等一些全域性的資訊, 而不必通過元件一層層傳遞。

以下是比較冗餘的傳遞:

<Page riderId={riderId} />
// ... which renders ...
<RiderDetail riderId={riderId} />
// ... which renders ...
<RiderLevel riderId={riderId} />
// ... which renders ...
<Avatar riderId={riderId} />
複製程式碼

Context 之前可以傳遞 <Avatar> 本身(Component Composition 的思想), 寫法如下:

function Page(props) {
  const avatar = <Avatar riderId={props.riderId} />
  return <RiderDetail avatar={avatar} />
}

<Page riderId={riderId} />
// ... which renders ...
<RiderDetail avatar={avatar} />
// ... which renders ...
<RiderLevel avatar={avatar} />
// ... which renders ...
{ props.avatar }
複製程式碼

接著是使用 Context 書寫的例子, 寫法如下:

const RiderContext = React.createContext(1) // 這裡為預設值

function Page(props) {
  const riderId = props.riderId
  return (
    <RiderContext.Provider value={riderId}>
      <RiderDetail />
    </RiderContext.Provider>
  )
}

function RiderDetail() {
  return <RiderLevel />
}

class RiderLevel extends React.Component {
  static contextType = RiderContext
  render() {
    return <Avatar avatar={this.context} />;
  }
}
複製程式碼

新的生命週期(16.3)

React 特性剪輯(版本 16.0 ~ 16.9)

在未來 17 的版本中,將移除的生命週期鉤子如下:

  • componentWillMount(): 移除這個 api 基於以下兩點考慮:
    • 服務端渲染: 在服務端渲染的情景下, componentWillMount 執行完立馬執行 render 會導致 componentWillMount 裡面執行的方法(獲取資料, 訂閱事件) 並不一定執行完;
    • Concurrent Render: 在 fiber 架構下, render 前的鉤子會被多次呼叫, 在 componentWillMount 裡執行訂閱事件就會產生記憶體洩漏;

遷移思路, 將以前寫在 componentWillMount 的獲取資料、時間訂閱的方法寫進 componentDidMount 中;

  • componentWillReceiveProps(nextProps): 移除這個 api 基於如下考慮:
    • 語義不太契合邏輯

舉個例子: 比如切換 tab 時都要重新獲取當前頁面的資料, 之前通常會這麼做:

componentWillReceiveProps(nextProps) {
  if (nextProps.riderId !== this.props.riderId) {
    fetchData()
  }
}
複製程式碼

新的鉤子 getDerivedStateFromProps() 更加純粹, 它做的事情是將新傳進來的屬性和當前的狀態值進行對比, 若不一致則更新當前的狀態。之前 componentWillReceiveProps() 裡的獲取資料的邏輯之前提到 Concurrent render 的時候也提到了應該後置到 componentDidUpdate() 中。

getDerivedStateFromProps(nextProps, prevState) {
  if (nextProps.riderId !== prevState.riderId) {
    return {
      riderId: nextProps.riderId
    }
  }
  // 返回 null 則表示 state 不用作更新
  return null
}
複製程式碼
  • componentWillUpdate(): 目前將其理解為和 componentWillMount 一樣的情況

在 React 16.3 的版本中,新加入了兩個生命週期:

  • getDerivedStateFromProps(nextProps, prevState): 更加語義化, 用來替代 componentWillMount()componentWillReceiveProps(nextProps);

  • getSnapshotBeforeUpdate(prevProps, prevState): 可以將該鉤子返回的結果傳入 componentDidUpdate 的第三個引數中, 從而達到 dom 資料統一。用來替代 componentWillUpdate();

具體 demo 可見 Update on Async Rendering

React.memo(16.6)

React.memo 是一個高階元件, 它使無狀態元件擁有有狀態組價中的 shouldComponentUpdate() 以及 PureComponent 的能力。

const MyComponent = React.memo(function MyComponent(props) {
  ...
})
複製程式碼

Hooks(16.7)

在 React 16.7 之前,React 有兩種形式的元件,有狀態元件(類)和無狀態元件(函式)。Hooks 的意義就是賦能先前的無狀態元件,讓之變為有狀態。這樣一來更加契合了 React 所推崇的函數語言程式設計。

接下來梳理 Hooks 中最核心的 2 個 api, useStateuseEffect

useState

useState 返回狀態和一個更新狀態的函式

const [count, setCount] = useState(initialState)
複製程式碼

使用 Hooks 相比之前用 class 的寫法最直觀的感受是更為簡潔

function App() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  )
}
複製程式碼

useEffect(fn)

在每次 render 後都會執行這個鉤子。可以將它當成是 componentDidMountcomponentDidUpdatecomponentWillUnmount 的合集。因此使用 useEffect 比之前優越的地方在於:

  1. 可以避免在 componentDidMount、componentDidUpdate 書寫重複的程式碼;
  2. 可以將關聯邏輯寫進一個 useEffect;(在以前得寫進不同生命週期裡);

React 的未來

React 特性剪輯(版本 16.0 ~ 16.9)

今年的 React Conf 的一張圖, 可以看到 React 從出來到現在勢頭呈穩健上升趨勢, 並在 2018 年這個節點上把 Jquery 拉下了王座。但可以看見 React 未來還有一段很長的路要走。

相關連結

相關文章