Why React Hooks

1、前言

1.1 爲什麼要優先使用 SFC(Stateless Function Component)

Stateless Function Component:html

const App = (props) => (
  <div>Hello, {props.name}</div>
)
複製代碼

Class Component:react

class App extends React.Component {
  render() {
    return (
      <div>Hello, {this.props.name}</div>
    )
  }
}
複製代碼

上面是兩個最簡單的 function component 和 class component 的對比,首先從行數上來看,3 << 7。git

再看 babel 編譯成 es2015 後的代碼:github

Function Component:web

"use strict";

var App = function App(props) {
  return React.createElement("div", null, "Hello, ", props.name);
};
複製代碼

Class Component:npm

去除了一堆 babel helper 函數數組

"use strict";

var App =
/*#__PURE__*/
function (_React$Component) {
  _inherits(App, _React$Component);

  function App() {
    _classCallCheck(this, App);

    return _possibleConstructorReturn(this, _getPrototypeOf(App).apply(this, arguments));
  }

  _createClass(App, [{
    key: "render",
    value: function render() {
      return React.createElement("div", null, "Hello, ", this.props.name);
    }
  }]);

  return App;
}(React.Component);

複製代碼

Function Component 僅僅是一個普通的 JS 函數,Class Component 由於 ES2015 不支持 class 的緣由,會編譯出不少和 class 相關的代碼。性能優化

同時由於 Function Component 的特殊性,React 底層或許能夠作 更多的性能優化bash

總的來講,如下點:babel

  • 更容易閱讀和單測
  • 寫更少的代碼,編譯出更加精簡的代碼
  • React Team 可正對這種組件作更加的性能優化

1.2 惱人的 bind(this)

在 React Class Component 中,咱們必定寫過不少這樣的代碼

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
    	name: 'rccoder',
    	age: 22
    },
    this.updateName = this.updateName.bind(this);
    this.updateAge = this.updateAge.bind(this);
  }
  
  render() {
    <div onClick={this.updateName}	
</div>
  }
}
複製代碼

固然這個錯不在 React,而在於 JavaScript 的 this 指向問題,簡單看這樣的代碼:

class Animate {
  constructor(name) {
    this.name = name;
  }
  getName() {
    console.log(this);
    console.log(this.name)
  }
}

const T = new Animate('cat');
T.getName();  // `this` is Animate Instance called Cat

var P = T.getName;
P(); // `this` is undefined
複製代碼

這個例子和上面的 React 一模一樣,在沒有 bind 的狀況下這樣寫會 致使了 this 是 global this,即 undefined。

解決它比較好的辦法就是在 contructor 裏面 bind this。

在新版本的 ES 中,有 Public Class Fields Syntax 能夠解決這個問題,即:

class Animate {
  constructor(name) {
    this.name = name;
  }
  getName = () => {
    console.log(this);
    console.log(this.name)
  }
}

const T = new Animate('cat');
T.getName();  // `this` is Animate Instance called Cat

var P = T.getName;
P(); // `this` is Animate Instance called Cat
複製代碼

箭頭函數不會建立本身的 this,只會依照詞法從本身的做用域鏈的上一層繼承 this,從而會讓這裏的 this 指向剛好和咱們要的一致。

即便 public class fileds syntax 藉助 arrow function 能夠勉強解決這種問題,但 this 指向的問題依舊讓人 「恐慌」。

1.2 被廢棄的幾個生命周圍

React 有很是多的生命週期,在 React 的版本更新中,有新的生命週期進來,也有一些生命週期官方已經漸漸開始認爲是 UNSAFE。目前被標識爲 UNSAFE 的有:

  • componentWillMount
  • componentWillRecieveProps
  • componentWillUpdate

新引入了

  • getDerivedStateFromProps
  • getSnapshotBeforeUpdate

getDerivedStateFromPropsgetSnapshotBeforeUpdate 均是返回一個處理後的對象給 componentDidUpdate,全部須要操做的邏輯都放在 componentDidUpdate 裏面。

原則上:

  • getDerivedStateFromProps + componentDidUpdate 能夠替代 componentWillReceiveProps 的全部正常功能;
  • getSnapshotBeforeUpdate + componentDidUpdate 能夠替代 componentWillUpdate 的全部功能。

具體的 緣由遷移指南 能夠參考 React 的官方博客:Update on Async Rendering,有比較詳實的手把手指南。

最後,你應該依舊是一樣的感受,Class Component 有如此多的生命週期,顯得是如此的複雜。


說了上面一堆看似和題目無關的話題,其實就是爲了讓你以爲 「Function Component 大法好」,而後再開心的看下文。


2、什麼是 React Hooks

終於來到了和 Hooks 相關的部分,首先咱們看下 什麼是 Hooks

2.1 什麼是 Hooks

首先來看下咱們熟知的 WebHook:

Webhooks allow you to build or set up GitHub Apps which subscribe to certain events on GitHub.com. When one of those events is triggered, we'll send a HTTP POST payload to the webhook's configured URL. Webhooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server. You're only limited by your imagination.

—— GitHub WebHook 介紹

核心是:When one of those events is triggered, we'll send a HTTP POST payload to the webhook's configured URL

2.2 什麼是 React Hooks

那 React Hooks 又是什麼呢?

Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.

看上去和 WebHook 區別不小,實際上也不小,怎麼解釋呢?

React Hook 在 Function Component 上開了一些 Hook,經過內置的一些 Hook 可讓 Function Component 擁有本身的 state 和 生命週期,同時能夠在 Hook 中操做它。此外經過組合內置的 Hook + 本身的業務邏輯 就能夠生成新的 Custom Hook,以方便優雅複用一些業務邏輯。

3、Hooks 以前的一些問題

3.1 不一樣組件間邏輯複用問題

不少時候,視圖表現不一樣的組件都指望擁有一部分相同的邏輯,好比:強制登陸、注入一些值等。這個時候咱們常常會使用 HOC、renderProps 來包裝這層邏輯,總的來講都會加入一層 wrapper,使組件的層級發生了變化,隨着業務邏輯複雜度的增長,都會產生 wrapper 地獄的問題。

3.2 複雜組件閱讀困難問題

隨着業務邏輯複雜度的增長,咱們的組件常常會在一個生命週期中幹多見事,好比:在 componentDidMount 中請求數據、發送埋點等。總之就是在一個生命週期中會寫入多個徹底不相關的代碼,進而形成各類成本的隱形增長。

假如由於這些問題再把組件繼續抽象,不只工做量比較繁雜,同時也會遇到 wrapper 地獄和調試閱讀更加困難的問題。

3.3 class component 遇到的一些問題

從人的角度上講,class component 須要關心 this 指向等,大多常常在使用 function component 仍是 class component 上感到困惑;從機器的角度上講,class component 編譯體積過大,熱重載不穩定

4、Hooks 有哪些功能

上述提到的三個問題,Hooks 某種意義上都作了本身的解答,那是如何解答的呢?

目前 Hooks 有: State Hook、Effect Hook、Context Hook、以及 Custom Hook。

4.1 State Hook

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

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

useState 是 State Hook 的 API。入參是 initialState,返回一個 數組,第一值是 state,第二個值是改變 state 的函數。

若是 initialState 的提升須要消耗大量的計算力,同時不指望這些計算阻塞後面要乾的事情的話。initialState 能夠是個函數,會在 render 前調用達到 Lazy Calc 的效果。

useState(() => {
  // ... Calc
  return initialState;
})
複製代碼

同時爲了不沒必要要的性能開銷,在設置 State 的時候若是兩個值是相等的,則也不會觸發 rerender。(判斷兩個值相等使用的是 Object.is

4.2 Effect Hook

import React, { useState, useEffect } from 'react';

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

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
    return () => {
      ... // Similar to componentWillUnMount。Named as clear up effect
    }

  });

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

useEffect 至關於 class Component 中 componentDidMountcomponentDidUpdatecomponentWillUnmount 三個生命週期的大綜合,在組件掛載、更新、卸載的時候都會執行 effect 裏面的函數。用 「after render」 理解是最好的。

值的注意的是,Effect Hook 中的內容不會像 componentDidMountcomponentDidUpdate 同樣阻塞渲染。若是不指望這種表現,但是用來 API 表現同樣的 useLayoutEffect。(常見的計算器快速增長問題)

在一個 Function Component 裏,和 useState 同樣能夠可使用屢次 useEffect,這樣在組織業務邏輯的時候,就能夠按照業務邏輯去劃分代碼片斷了(而不是 Class Component 中只能按照生命週期去劃分代碼片斷)。

Effect Hook 的執行實際是 「after render」,爲了不每一個 render 都執行全部的 Effect Hook,useEffect 提供了第二個入參(是個數組),組件 rerender 後數組中的值發生了變化後纔會執行該 Effect Hook,若是傳的是個空數組,則只會在組件第一次 Mount 後和 Unmount 前調用。這層優化理論上是能夠在編譯時去作的,React Team 後期可能會作掉這層。

4.3 Custom Hook

import React, { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li>
  );
}
複製代碼

useFriendStatus 就是一個典型的 Custom Hook,他利用 useState 和 useEffect 封裝了 訂閱朋友列表,設置朋友狀態,組件卸載時取消訂閱 的系列操做,最後返回一個表示是否在線的 state;在使用的時候,就能夠像內置 Hook 同樣使用,享用封裝的系列邏輯。

在 Hooks 內部,即便在一個 Function Component 中,每一個 Hooks 調用都有本身的隔離空間,能保證不一樣的調用之間互不干擾。

useFriendStatususe 開頭是 React Hooks 的約定,這樣的話方便標識他是一個 Hook,同時 eslint 插件也會去識別這種寫法,以防產生沒必要要的麻煩。

同時若是 useXXXinitialState 是個變量,而後這個變量發生變化的時候,該 Hook 會自動取消以前的消息訂閱,從新進行 Hooks 的掛載。也就是說,在上面的例子中,若是 props.friend.id 發生變化,useFriendStatus 這個 Hooks 會從新掛載,進而 online 狀態也會正常顯示。

4.4 Context Hook

function Example() {
  const locale = useContext(LocaleContext);
  const theme = useContext(ThemeContext);
  // ...
}
複製代碼

useContext 的入參是某個 Provider 提供的 context,若是 context 發生變化的話,返回值也會當即發生變化。

4.5 Reduce Hook

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter({initialState}) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <> Count: {state.count} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); } 複製代碼

若是 State 的變化有比較複雜的狀態流轉,可使用 useReducer 讓他更加 Redux 化,以便讓這層邏輯更加清晰。同時 Reduce Hook 也提供 Lazy Calc 的功能,有需求的時候能夠去使用它。

除此以外,內置的 Hooks 還有 useCallbackuseMemouseRefuseImperativeHandleuseLayoutEffectuseDebugValue。能夠去 這裏 瞭解更多的用法。

5、例子對比

該例子是 Dan 在 React Conf 上的例子,算是很是有表明性的了:

視頻地址:React Today and Tomorrow and 90% Cleaner React With Hooks

同時這裏有一些 Hooks,看看實現和所解決的問題能更深的去了解 Hooks 的魅力:react hooks codesandbox

6、到底何時會用到 React Hook

  • 更喜歡寫 Function Component
  • 想讓 Function Component 用有 state 和生命週期
  • 厭惡 class component 中按照 生命週期 去拆分代碼區塊(而不是按照 業務邏輯 拆分代碼區塊)
  • 想提煉不一樣 UI 組件的共有業務邏輯又不想由於 HOC 或者 renderProps 陷入 wrapper 地獄

7、引入的問題

7.1 奇怪的 useEffect

useEffect 能夠覆蓋 componentDidMount,ComponentDidUpdate 和 componentWillUnmount 三個生命週期的操做,但從某種意義上說,實現 componentWillUnMount 的操做是有點讓人窒息的,若是是一個沒看過文檔的人,絕對不知道要這麼操做。

useEffect(() => {
  // cDM or cDU
  return () => {
    // cWU
  }
})
複製代碼

7.2 底層實現致使邏輯上的問題

React Hook 在內部實現上是使用 xxx,由於使用 React Hook 有兩個限制條件

  • 只能在頂層調用 Hooks,不能在循環、判斷條件、嵌套的函數裏面調用 Hooks。 這樣才能保證每次都是按照順序調用 Hooks,不然從 tuple 裏拿到的值就不必定是你要的那個 tuple 裏的值了,Effect 也是一樣的道理。具體緣由大概是內部維護了一個隊列來表示 Hooks 執行的的順序,而順序正式定義的時候定義的,若是再也不最頂層,可能會致使執行時 Hooks 的順序和定時時的不一致,從而產生問題,更加詳細的能夠參考 React 官方的解釋:explanation
  • 只容許 Function Component 和 Custom Hooks 調用 React Hook,普通函數不容許調用 Hooks。

React Team 爲此增長了 eslint 插件:eslint-plugin-react-hooks,算是變通的去解決問題吧。

8、常見疑問

8.1 爲何 useState 返回的是個數組

這裏的數組,從某種意義上說叫 tuple 會更加容器理解一些,惋惜 JavaScript 中目前還沒這種概念。

同時,若是返回的是個 Object 又會怎麼樣呢?

let { state: name, setState: setName } = useState('Name');
let { state: surname, setState: setSurname } = useState('Surname');
複製代碼

看起來好像是更糟糕的

8.2 性能問題

shouldComponentUpdate 使用 useMemo 便可,參考:How to memoize calculations?

同時 Hooks 的寫法避免的 Class Component 建立時大量建立示例和綁定事件處理的開銷,外加用 Hooks 的寫法能夠避免 HOC 或者 renderProps 產生深層嵌套,對 React 來講處理起來會更加輕鬆。

8.3 能覆蓋 Class Component 的全部生命週期麼?

getSnapshotBeforeUpdatecomponentDidCatch 目前覆蓋不到

8.4 prevState 如何獲取

藉助 useRef 獲取

若是有其餘問題,不妨去 React Hooks FQA 看看,大機率裏面涵蓋了你想知道的問題。

9、參考資料

原文地址:github.com/rccoder/blo… (去這交流更方便哦~)

相關文章
相關標籤/搜索