談談React Hooks 與Vue3.0 Function based API的區別?

hi~ 豆皮粉兒. 又見面啦~javascript

2dced3bf-e7dc-49aa-839a-743ade91348a.gif

React Hooks release 已經有很長一段時間了,Vue3.0 也帶來了 Function based API,都咱們提供了新的模式來複用和抽象邏輯,那這二者有什麼區別的,saucxs 給你們帶了解讀html

做者: 鬆寶寫代碼vue

1、前言

React Hooks 是 React16.8 引入的新特性,支持在類組件以外使用 state、生命週期等特性。java

Vue Function-based API 是 Vue3.0 最重要的 RFC (Requests for Comments),將 2.x 中與組件邏輯相關的選項以 API函數 的形式從新設計。react

目錄:git

  • React Hooks
    • React Hooks是什麼
    • useState Hook
    • useEffect Hook
    • React Hooks 引入的緣由以及設計原則
    • React Hooks 使用原則及其背後的原理
  • Vue3.0 Function-based API
    • 基本用法
    • 引入的緣由以及解決的問題
  • React Hooks 與 Vue3.0 Function-based API 的對比

2、React Hooks

React Hooks 是什麼?

引用官網的一段話:github

從概念上講,React 組件更像是函數。而 Hooks 則擁抱了函數,同時也沒有犧牲 React 的精神原則。Hooks 提供了問題的解決方案,無需學習複雜的函數式或響應式編程技術。編程

另外,Hooks 是100%向後兼容的,也是徹底可選的。設計模式

React Hooks 提供了三個基礎 Hook : useStateuseEffectuseContext,其餘 Hooks 可參考React Hooks APIapi

useState Hook

下面是一個實現計數器功能的類組件示例:

import React from 'react';

class Example extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      count: 0
    }
  }

  render () {
    return (
      <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({count: this.state.count + 1)}}>Click me</button> </div>
    )
  }
}
複製代碼

需求很簡單,設定初始值爲0,當點擊按鈕時,count 加 1。

當使用 useState Hook 實現上述需求時:

import React, { useState } from 'react';

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

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

其中 useState Hook 作了哪些事情呢?

  1. 在調用 useState 方法時,定義了一個state變量count,它與類組件中的this.state功能徹底相同。對於普通變量,在函數退出後即消失,而state中的變量會被 React 保留。
  2. useState 方法只接收一個參數,那就是初始值。useState 方法一次只定義一個變量,若是想在state中存儲兩個變量,只須要調用兩次 useState() 便可。
  3. useState 的返回值是一個由 當前state 以及 更新state的函數 組成的數組。這也是採用 數組解構 方式來獲取的緣由。

在使用 Hooks 實現的示例中,會發現 useState 讓代碼更加簡潔了:

  • 獲取state:類組件中使用 this.state.count ,而使用了 useSatet Hook 的函數組件中直接使用 count 便可。
  • 更新state:類組件中使用 this.setState() 更新,函數組件中使用 setCount() 便可。

這裏拋出幾個疑問,在講解原理的地方會進行詳細解釋:

  • React 是如何記住 useState 上次值的?
  • React 是如何知道 useState 對應的是哪個組件?
  • 若是一個組件內有多個 useState,那從新渲染時 useState 的取值順序又是怎麼肯定的?

useEffect Hook

在講 useEffect 以前,先講一下 React 的反作用。

在 React 中,數據獲取、訂閱或者手動修改DOM等操做,均被稱爲 '反作用',或者 '做用' 。

而 useEffect 就是一個 Effect Hook ,爲函數組件添加操做反作用的能力,能夠把它看做是類組件中的componentDidMountcomponentDidUpdatecomponentWillUnmount三個周期函數的組合。

下面是一個關於訂閱的例子:

import React from 'react';

class Example extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      isOnline: null
    }
  }

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentDidUpdate(prevProps) {
    // 當 friend.id 變化時進行更新
    if (prevProps.friend.id !== this.props.friend.id) {
      // 取消訂閱以前的 friend.id
      ChatAPI.unsubscribeFromFriendStatus(
        prevProps.friend.id,
        this.handleStatusChange
      );
      // 訂閱新的 friend.id
      ChatAPI.subscribeToFriendStatus(
        this.props.friend.id,
        this.handleStatusChange
      );
    }
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange = (state) => {
    this.setState({
      isOnline: status.isOnline
    });
  }

  render () {
    return (
      <div> {this.state.isOnline === null ? 'Loading...' : (this.state.isOnline ? 'Online' : 'Offline') } </div>
    )
  }
}
複製代碼

從上述的代碼中不難發現,存在必定的重複代碼,邏輯不得不拆分在三個生命週期內部。另外因爲類組件不會默認綁定 this ,在定義 handleStatusChange 時,還須要爲它 綁定this

這裏補充一點,對於類組件,須要謹慎對待 JSX 回調函數中的 this,類組件默認是不會綁定 this 的,下面提供幾種綁定 this 的方法:

  1. 一般的作法是將事件處理函數聲明爲 class 中的方法,如:constructor內部 this.handleClick = this.handleClick.bind(this)
  2. 在 onClick 內部使用箭頭函數, 如:onClick={e=>this.handleClick(id, e)},注意:該方法在每次渲染時都會建立不一樣的回調函數。在大多數狀況下,沒什麼問題,但若是該回調函數做爲 prop 傳入子組件時,這些組件可能會進行額外的從新渲染。
  3. 在 onClick 內部使用 bind 綁定, 如:onClick={this.handleClick.bind(this, e)}
  4. 使用 class fields 綁定:如:handleClick = (e) => {}

這也是 React 引入 Hooks 的其中一個緣由。

下面讓咱們看一下 useEffect Hook 是如何實現上述需求的:

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

function Example(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    const handleStatusChange = (status) => {
      setIsOnline(status.isOnline)
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  }, [props.friend.id]);

  return (
    <div> {isOnline === null ? 'Loading...' : (isOnline ? 'Online' : 'Offline') } </div>
  )
}
複製代碼

在上述例子中,你可能會對 useEffect Hook 產生如下疑問:

  1. useEffect 作了什麼?首先,接收了一個函數,而 React 會保存這個函數(稱爲'effect'),而且在執行 DOM 更新後調用這個函數,即添加訂閱。
  2. [props.friend.id] 參數設置了什麼?
    • 默認狀況下,useEffect 接收一個函數做爲第一個參數,在不設置第二個參數時,每一次渲染都會執行 effect,這有可能會致使性能問題。
    • 爲了解決這個問題,可添加第二個參數,用來控制何時將會執行更新,這時候就至關於 componentDidUpdate 作的事情。
    • 若是想只運行一次 effect ,即僅在組件掛載和卸載時執行,第二個參數可傳遞一個空數組[]
  3. 在 useEffect 中爲何要返回一個函數呢?這是一個可選的操做,每個 effect 均可以返回一個清除函數。在 React 中,有一些反作用是須要清除的,好比 監聽函數、定時器等,這時候就須要爲 effect 增長一個返回函數,React 會在組件卸載的時候執行清除操做。

Hooks 所提供的功能遠不止這些,更多詳細的介紹能夠查閱官網文檔

React Hooks 引入的緣由以及設計原則

React Hooks 具體解決了什麼問題呢? React 爲何要引入這一特性呢?

主要有如下三點緣由:

  1. 在組件之間複用狀態邏輯很困難。
    • React 並無提供將可複用行爲附加到組件的途徑,通常比較常見的方法是採用 render props高階組件 解決。
    • React Hooks 支持 自定義Hook,能夠將狀態邏輯從組件中提出,使得這些邏輯可進行單獨測試、複用,在無需修改組件結構的狀況下便可實現狀態邏輯複用。點擊查看自定義Hook使用說明
  2. 複雜組件變得難以理解。
    • 每一個生命週期函數內部邏輯複雜、功能不單一,相互關聯的需求被拆分在各個生命週期中,而不相關的代碼卻須要在同一個週期內部進行整合。
    • 爲了解決這個問題,React Hooks 將組件中相互關聯的部分拆分紅更小的函數,引入了 Effect Hook,如上述 useEffect 的示例,正是解決了這個問題。
  3. 難以理解的class。
    • this綁定
    • 打包尺寸,函數經過ES export導出,能夠藉助 tree-shaking 把沒用到的函數移除;壓縮混淆,類的屬性和方法無法壓縮
    • class熱重載不穩定

React Hooks 設計原則

主要有如下四點:

  1. 優雅高效的複用狀態邏輯
  2. 無 class 困擾
  3. 具有 class 已有的能力
  4. 功能單一的反作用

下面咱們根據幾個例子來感覺 React Hooks 具體是如何體現的。

一、優雅高效的複用狀態邏輯

在以前,狀態邏輯的複用通常是採用 Mixins APIRender PropsHOC實現,可是因爲Render Props 與 HOC 自己也是組件,狀態邏輯的複用也是經過封裝組件的形式來實現,仍難以免組件多層嵌套的問題,也比利於後續的理解與維護。

在 React Hooks 中,提供了 自定義Hook 來實現狀態邏輯的複用。

好比 在聊天程序中,使用訂閱獲取好友的狀態:

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

function useOnline(id) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange (state) {
      setIsOnline(status.isOnline)
    }
    ChatAPI.subscribeToFriendStatus(id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(id, handleStatusChange);
    };
  }, [id]);

  return isOnline;
}

// 使用 自定義Hook
function Example(props) {
  const isOnline = useOnline(props.friend.id);
  
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
複製代碼

能夠看到 useOnline 組件的邏輯是與業務徹底無關的,它只是用來添加訂閱、取消訂閱,以獲取用戶的狀態。

總結:

  • 數據來源清楚,之間根據函數返回值得到
  • 代碼量少,更易維護
  • 避免重複建立組件帶來性能損耗

注意

  • 自定義Hook 是一個函數,名稱必須以 'use' 開頭,這是一個約定,若是不遵循,React 將沒法自動檢測是否違反了 Hook 規則。
  • 在函數的內部能夠調用其餘 Hook,可是請確保只在頂層無條件地調用其餘Hook。
  • React 會根據名稱來檢測 Hook 是否違反了規則。
  • 自定義 Hook 是一種重用狀態邏輯的機制,每次使用時,內部 state 與反作用是徹底隔離的。

二、無 class 困擾

下面咱們將根據一個具體例子 實現根據點擊事件,控制節點的展現或者隱藏的需求,來對 Render PropsHOCHooks的實現方式作簡單對比。

使用 Render Props 實現

Render Props 是指一種在 React 組件之間使用一個值爲函數的 prop 共享代碼的簡單技術。

建立 VisibilityHelper

import React from 'react';
import PropTypes from 'prop-types';
class VisibilityHelper extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isDisplayed: props.initialState || false,
    };
  }
  hide = () => {
    this.setState({
      isDisplayed: false,
    });
  }
  show = () => {
    this.setState({
      isDisplayed: true,
    });
  }
  render() {
    return this.props.children({
      ...this.state,
      hide: this.hide,
      show: this.show,
    });
  }
}
VisibilityHelper.propTypes = {
  initialState: PropTypes.bool,
  children: PropTypes.func.isRequired,
};
export default VisibilityHelper;
複製代碼

VisibilityHelper 的使用:

import React from 'react';
import VisibilityHelper from 'VisibilityHelper';

function ButtonComponent() {
  return (
    <VisibilityHelper initialState={true}> { ({ isDisplayed, hide, show }) => ( <div> { isDisplayed ? <button onClick={hide}>Click to hide</button> : <button onClick={show}>Click to display</button> } </div> ) } </VisibilityHelper>
  );
}
複製代碼

<ButtonComponent>組件中,咱們使用了一個帶有函數prop<VisibilityHelper>組件,實現了代碼複用。

使用 HOC 實現

高階組件,是 React 中複用組件邏輯的一種高級技巧,是一種基於 React 組合特性而造成的設計模式。

定義高階組件 VisibilityHelper ,注意 HOC 不會修改傳入的組件,也不會使用繼承來複制其行爲。相反,HOC 經過將組件包裝在容器組件中來組成新組件。HOC 是純函數,沒有反作用。

import React from 'react';
function VisibilityHelper(WrappedComponent, initialState = false) {
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        isDisplayed: initialState
      };
    }

    hide = () => {
      this.setState({
        isDisplayed: false,
      });
    }
    show = () => {
      this.setState({
        isDisplayed: true,
      });
    }

    render() {
      return <WrappedComponent isDisplayed={this.state.isDisplayed} show={() => this.show()} hide={() => this.hide()} {...this.props} />;
    }
  };
}
// 定義 按鈕組件
let ButtonComponent = ({ isDisplayed, hide, show }) => {
  return (
    <div> { isDisplayed ? <button onClick={hide}>Click to hide</button> : <button onClick={show}>Click to display</button> } </div>
  );
}

// 使用高階組件,並設定默認值
ButtonComponent = VisibilityHelper(ButtonComponent, true);
export default ButtonComponent
複製代碼
在 React Hooks 中是如何實現上述邏輯的呢?
import React, { useState } from 'react';
function ButtonComponent() {
  const [isDisplayed, show] = useState(initialState || false)
  return (
    <div> { isDisplayed ? <button onClick={() => show(false)}>Click to hide</button> : <button onClick={() => show(true)}>Click to display</button> } </div>
  )
}
複製代碼

從對比中能夠發現,使用 Hooks 更簡潔,且不須要在擔憂 this 綁定地問題。

三、具有 class 已有的能力

對於經常使用的 class 能力,Hooks 已經基本覆蓋。

對於其餘不常見的能力,官方給出的迴應是:

目前暫時尚未對應不經常使用的 getSnapshotBeforeUpdate 和 componentDidCatch 生命週期的 Hook 等價寫法,但咱們計劃儘早把它們加進來。目前 Hook 還處於早期階段,一些第三方的庫可能還暫時沒法兼容 Hook。

四、功能單一的反作用

經過文中的幾個示例,應該能夠了解到 useEffect Hook 即是設計用來解決反作用分散、邏輯不單一的問題。

在真實的應用場景中,可根據業務須要編寫多個 useEffect。

React Hooks 使用原則

兩條使用原則:

  1. 只在最頂層使用 Hooks,不能在循環、條件或嵌套函數中調用 Hooks。這是爲了保證 Hooks 在每一次渲染中都按照一樣的順序被調用。
  2. 只能在 React 的函數組件中或者 自定義Hook 中調用 Hooks。確保組件的狀態邏輯在代碼中清晰可見。

這兩條原則讓 React 可以在屢次的 useState 和 useEffect 調用之間保持 hook 狀態的正確。

React Hooks 背後的原理

以前的拋出的疑問:

  • React 是如何記住 useState 上次值的?
  • React 是如何知道 useState 對應的是哪個組件?
  • 若是一個組件內有多個 useState,那從新渲染時 useState 的取值順序又是怎麼肯定的?

在 React 中從編譯到渲染成 Dom,都要經歷這樣的過程:JSX -> Element -> FiberNode -> Dom

Hooks 要想和一個函數組件進行綁定, 就要和這個轉換過程的某個節點進行關聯,因爲 Hooks 只有在 render 過程當中進行調用,很明顯就只能關聯到 FiberNode 上。

在 FiberNode 上有 一個屬性 memoizedState,這個屬性在 class 組件中對應最終渲染的 state。

class 組件的state通常是一個對象,在 函數組件中變成 一個鏈表,如 class 組件 memoizedState = {a: 1, b: 2} => 函數組件 memoizedState = {state: 1, next: {state: 2, next: ..}}

每一個鏈表的節點都是一個 useState,從而將全部 Hooks 進行串聯起來。不只僅 State Hook,其它 Hook 也是經過 memoizedState 串聯起來的。

第一次渲染後,經過 FiberNode 的 memoizedState 將全部 Hook 進行收集完成。

當執行 setState 進行組件更新時,從新執行函數組件,這時會從收集的 Hooks 中按照執行順訊依次取出,對於 State Hook 會進行計算將最新的值返回, Effect Hook 會在組件渲染結束後,先執行清除函數,再執行 反作用函數。

過程如圖: 過程

3、Vue3.0 Function-based API

首先提一下 Vue Function-based API 的升級策略。

vue官方提供了Vue Function API,支持Vue2.x版本使用組件邏輯複用機制。3.x無縫替換掉該庫。

另外,Vue3.x發佈支持兩個不一樣版本:

  • 兼容版本:同時支持新 API 和 2.x 的全部選項
  • 標準版本:只支持新 API 和部分 2.x 選項

下面是一個基礎例子:

import { value, computed, watch, onMounted } from 'vue'

const App = {
  template: ` <div> <span>count is {{ count }}</span> <span>plusOne is {{ plusOne }}</span> <button @click="increment">count++</button> </div> `,
  setup() {
    // reactive state
    const count = value(0)
    // computed state
    const plusOne = computed(() => count.value + 1)
    // method
    const increment = () => { count.value++ }
    // watch
    watch(() => count.value * 2, val => {
      console.log(`count * 2 is ${val}`)
    })
    // lifecycle
    onMounted(() => {
      console.log(`mounted`)
    })
    // expose bindings on render context
    return {
      count,
      plusOne,
      increment
    }
  }
}
複製代碼

Vue Function-based API 引入的緣由及解決的問題

引入的緣由,借用官方推出的一段話:

組件 API 設計所面對的核心問題之一就是如何組織邏輯,以及如何在多個組件之間抽取和複用邏輯。

其實也就是 React Hooks 引入時提到的:在組件之間複用狀態邏輯很困難。

在Vue2.0中,有一些常見的邏輯複用模式,如:Mixins高階組件Renderless Components,這些模式均或多或少的存在如下問題:

  • 模版中的數據來源不清晰
  • 命名空間容易衝突
  • 性能問題,須要額外的組件實例嵌套來封裝邏輯,帶來沒必要要的性能開銷等

Function-based API 受 React Hooks 的啓發,提供一個全新的邏輯複用方案,且不存在上述問題。

4、React Hooks 與 Vue Function-based API 的對比

二者均具備基於函數提取和複用邏輯的能力。

React Hooks 在每次組件渲染時都會調用,經過隱式地將狀態掛載在當前的內部組件節點上,在下一次渲染時根據調用順序取出。而 Vue 的響應式 機制使 setup() 只須要在初始化時調用一次,狀態經過引用儲存在 setup() 的閉包內。這也是vue不受調用順序限制的緣由。

相關文章
相關標籤/搜索