從React函數式組件說到Hooks

1.前言介紹

歷史
React在2013年開源,在2015引入函數式組件,不過在平常開發中常常被忽略。
ReactJS Core Team 確實大部分紅員都曾在推特上公開誇讚過對函數式編程 與 ML 系語言(或其特性)的優勢:Sebastian 平常提到 OCaml,Sophie 至少提過 Rust/Kotlin/Swift 而且嫌棄過 Dart 沒有 ADT 和 non-nullability,Dan 和 Andrew 的 Redux 主要受 Elm 影響,Dan 曾提過學習高階函數後省去了一半設計模式,Andrew 的 Recompose 主要利用了函數的組合性,其餘成員未知。前端

爲何引入class組件:vue

Chenglou: God forbid React api led newcomers into something familiar instead of FP purists’ agendas lol。
難道還不容許 React API 設計得對新人更友好了?
Cristiano: So I guess also making React in JS in the first place was the ultimate trolling.
咱們先把 React 作成 JS 就是找罵啊……
Jordan: They're onto us.
這怎麼怪到咱們頭上了……

事實是,即便在 FB 內部,也顯然不是全部程序員都熟悉函數式編程的概念。作一個前端框架是拿來用的,若是有你們熟悉的概念能夠對照,爲何刻意要去用你們不熟悉的概念呢?對於遷移至 ES6 Class,主要的目的是爲了減小 React 特有的 API,畢竟 並不由於它「接受一個函數」就比 ES6 class 更函數式了……react

1562660893858-fdc42055-9900-4add-a34c-07ab07b909f9.png

2.函數式組件和類組件的區別

在以前class組件一般被認爲有更多的功能,例如能夠擁有state,能夠調用生命週期,而如今在hooks引入以後這個說法就不成立了。之前Function組件沒有state,因此也叫SFC(stateless functional component),如今更新叫作FC(functional component)。或許你也據說過,這兩類組件中,有一類的性能更好。哪一類呢?不少這方面的性能測試,都是 有缺陷的 ,所以要從這些測試中 得出結論 ,不得不謹慎一點。性能主要取決於你代碼要實現的功能(以及你的具體實現邏輯),和使用函數組件仍是類組件,沒什麼關係。咱們觀察發現,儘管函數組件和類組件的性能優化策略 有些不一樣 ,可是他們性能上的差別是很微小的。程序員

構建時的差異

先看下面兩個組件:
函數式組件在父組件中執行的函數的方法:編程

function Greeting() {
  return <p>Hello</p>;
}

// Inside React
const result = Greeting(props); // <p>Hello</p>

類組件在父組件中先實例化一個對象,而後執行這個對象上的render方法:設計模式

class Greeting extends React.Component {
  render() {
    return <p>Hello</p>;
  }
}

// Inside React
const instance = new Greeting(props); // Greeting {}
const result = instance.render(); // <p>Hello</p>

運行時的差異

再來看兩段代碼
一個兩秒後執行alter的按鈕的函數式組件api

function ProfilePage(props) {
    const showMessage = () => {
      alert('我是 ' + props.user);
    };
  
    const handleClick = () => {
      setTimeout(showMessage, 2000);
    };
    
    return (
      <button onClick={handleClick}>Function</button>
    );
  }

一個兩秒後執行alter的按鈕的函數式組件數組

class ProfilePage extends React.Component {
    showMessage = () => {
      alert('我是' + this.props.user);
    };
  
    handleClick = () => {
      setTimeout(this.showMessage, 2000);
    };
    
  
    render() {
      return <button onClick={this.handleClick}>Class</button>;
    }
  }

通常咱們會認爲上面兩個組件是徹底等價的。開發者常常像上面這樣,在函數組件和類組件之間重構代碼,卻沒有意識到他們隱含的差別。然而這兩個是有一些差異,你有看出這兩個的區別嗎?
咱們用一個組件將上面兩個組件包裹緩存

function App() {
  const [user, setUser] = useState('小碼王')
  return (
    <div className="App">
      <p>{user}</p>
      <button onClick={() => setUser('小碼世界')}>changeName</button>
      <br/>  
      <ClassButton user={user}/>
      <FunctionButton user={user}/>
    </div>
  );
}

當咱們先點擊Class按鈕而後再點擊changName按鈕,alert出的我是小碼世界,而咱們先點擊Function再點擊changName,alter出的我是小碼王。
爲何會這樣呢?
咱們能夠看到在函數式組件中user是在props中讀取的,而在類組件中user是從this.props中讀取的,區別就在this上,在react中props是不可變,而this是可變的,因此當父組件的值改變時this也改變了。
有什麼方法使類組件也實現函數式組件的效果呢?性能優化

class ProfilePage extends React.Component {
  render() {
    // Capture the props!
    const props = this.props;

    const showMessage = () => {
      alert('我是 ' + props.user);
    };

    const handleClick = () => {
      setTimeout(showMessage, 2000);
    };

    return <button onClick={handleClick}>Class</button>;
  }
}

在render函數中創造了一個props的閉包,這個問題就解決了,是否是感受段代碼比較熟悉,其實在render函數裏面的代碼抽離出來就是函數式的組件了。因此當函數式組件有render方法之外的功能,就能夠實現類組件的任何功能了,這就是hooks了。

3. Hooks

1561787694247-821e5101-870f-43c3-80fd-ccd002c345fa.png

在 Hooks 的協議設計考量上,與本來的 React HoC Design 其實會有不少的不一樣,主要緣由也許就在於 HoC 總的來講是 Props 導向的設計,而 Hooks 則是更 functional 風格的調用形態。而且,HoC 可能會傾向於避免產生過多的 HoC 層級,而產生過深的 VDOM Tree。Hooks 大能夠更自由地講更多的 use 方法分離式的調用,也可以獲得更大的自由度;

Hooks解決的問題

  • 代碼重用:在hooks出來以前,常見的代碼重用方式是HOCs和render props,這兩種方式帶來的問題是:你須要解構本身的組件,同時會帶來很深的組件嵌套
  • 複雜的組件邏輯:在class組件中,有許多的lifecycle 函數,你須要在各個函數的裏面去作對應的事情。這種方式帶來的痛點是:邏輯分散在各處,開發者去維護這些代碼會分散本身的精力,理解代碼邏輯也很吃力
  • class組件的困惑:對於初學者來講,須要理解class組件裏面的this是比較吃力的(這個理由有點勉強~),同時,基於class的組件難以優化(舉個不恰當的例子,看一下babel轉移出來的class代碼量增加了多少)

從react team 公佈 hooks的概念時,淘寶內部作了一些簡單的研究與調研,結論是hooks是類react體系的將來主流編程模式,不管是基於hooks後業務代碼會更簡練仍是複用更容易,這些都是下降了react 編程的門檻。
尤雨溪也稱vue3.0的特性吸收了不少hooks的靈感,並在最新的RFC(意見徵求稿)中發佈了Function API + Hooks 。

Hooks的API

React Hook讓無狀態組件擁有了許多隻有有狀態組件的能力,如自更新能力(setState,使用useState),訪問ref(使用useRef或useImperativeMethods),訪問context(使用useContext),使用更高級的setState設置(useReducer),及進行相似生命週期的階段性方法(useEffect或useLayoutEffect)。

hooks之間的關係

useState useReducer useRef關係

function useReducer(reducer, initialArg,init){     
  var initialState = void 0;      
  if (init !== undefined) {        
    initialState = init(initialArg);
  } else {        
    initialState = initialArg;
  }
  function dispatch(action){          
    memoizedState = reducer(memoizedState,action);          
    render();      
  }      
  memoizedState =  memoizedState||initialState;      
  return  [memoizedState, dispatch]; 
}

function useState(initialState){    
  return  useReducer((oldState, newState)=>newState, initialState); 
}
​useRef() ===  useState({current: initialValue })[0]

useEffect useLayoutEffect的關係

resize,w_1492

useMemo useCallback的關係

useCallback和useMemo的參數跟useEffect一致,他們之間最大的區別有是useEffect會用於處理反作用,而前兩個hooks不能。

useMemo和useCallback都會在組件第一次渲染的時候執行,以後會在其依賴的變量發生改變時再次執行;而且這兩個hooks都返回緩存的值,useMemo返回緩存的變量,useCallback返回緩存的函數。
相關文章
相關標籤/搜索