函數式組件 && React Hook

React Component

binlive前端開發,web開發,node,vue,react,webpack
Class組件是咱們如今最多見的組件用法,它擁有組件內的狀態和生命週期等一些特性。Class組件是最多見的React組件寫法,它須要聲明一個class並去繼承 react 的Component 類。類組件使用範圍普遍,它擁有內部狀態、生命週期等一系列方法。
binlive前端開發,web開發,node,vue,react,webpack
函數式組件寫法更加簡潔,它接收父級傳入的 props 對象並返回一個 React 元素,它本質上就是基礎的 JavaScript函數

Class Component

React createClass

Class組件的寫法建立方法有兩種,下圖是兼容es5寫法的React.createClass定義的組件,這也是react一開始的建立組件的方法。 前端

binlive前端開發,web開發,node,vue,react,webpack

寫法說明:
  1. propTypes用來處理校驗定義屬性的類型校驗,PropTypes是從React中獲取的,在React16.0.0版本之後廢棄了這種寫法,PropTypes被單獨提取到prop-types包中,因此先比較新的版本中,咱們要進行類型校驗,須要手動引入 prop-types 這個模塊
  2. getInitialState用來初始化state,getDefaultProps用來定義props的默認屬性值,這兩個函數的用法有點相似於如今vue中的data和props方法
  3. createClass 組件須要定義一個render函數,它會返回 react node用來渲染視圖
ES6的class react

這是一個如今常見的ES6的class react組件寫法 vue

binlive前端開發,web開發,node,vue,react,webpack

寫法說明:
  1. React 在 v0.13.0 引入了 Component API, 配合一些代碼轉換工具咱們可使用最新的es語法
  2. 組件一樣須要定義一個render函數用來渲染視圖,因爲es6支持了方法簡寫,render以及其餘定義的函數已經不須要聲明function 關鍵字
  3. 在構造函數中初始化了 state,這裏能看到有一個 super 調用,ES6 規定,子類的構造函數必須執行一次super方法,若是子類沒有定義constructor方法,這個構造函數會被默認添加並調用super。 若是咱們聲明瞭constructor函數,就必須在構造函數裏調用super,只有在正常調用super(props)方法後才能夠訪問this對象,在constructor中若是要訪問this.props須要在super中傳入props,固然也能夠直接經過props訪問。可是不管有沒有定義constructor,super是否傳入props參數,在react的其餘生命週期中this.props都是可使用的,這是React自動附帶的。
  4. 繼承Component API 建立的組件遵循es6 class的規則,react不會幫咱們自動綁定this,爲了能正確訪問this,咱們須要在構造函數裏綁定this的指向。可是若是方法不少的話,就會週期構造函數裏寫了不少冗餘的綁定函數代碼
函數this優雅的解決方案

binlive前端開發,web開發,node,vue,react,webpack
這裏要說到 類字段語法這個提案 公有類字段的語法定義是 字段名 = 引用值。 在 constructor 方法被執行前, 實例上已經被賦值了該字段內容。

binlive前端開發,web開發,node,vue,react,webpack
因此如今比較流行的寫法是這樣的 根據公有類字段語法 咱們能夠直接經過 字段名 = state 對象內容來聲明state對象。 因爲箭頭函數沒有本身 this,在這裏它會指向當前類的實例。借用公有類字段的提案,咱們能夠直接聲明一個 箭頭函數來修正方法的this指向。 這樣咱們就無需在構造函數裏去建立state。也不須要在構造函數裏寫一大堆function綁定this。若是沒有其餘的操做,咱們就徹底能夠省略構造函數。

React.PureComponent

binlive前端開發,web開發,node,vue,react,webpack

PureComponent 內部是繼承React.Component來的,在React.Component組件中咱們可使用shouldComponentUpdate來判斷是否重發從新渲染,而 React.PureComponent 內部使用 shallowEqual 進行淺比較。 淺比較會比較更新先後的state和props的長度是否一致,判斷是否新增或者減小了props或state的數據個數。 而後它還會調用內部的objectis方法淺層對比先後的state和prop,objectis相似於es6的 Object.is方法, Object.is 相似於 === 全等運算符,只是在比較 +0 跟 -0 時表現的不太同樣, === 返回的是true 而他是false。node

binlive前端開發,web開發,node,vue,react,webpack
因爲淺比較的緣由,當props中的參數爲引用類型時,修改對象數據中的value時, PureComponent 組件不會觸發從新渲染,由於他們是引用的同一個內存地址。

Functional Component

binlive前端開發,web開發,node,vue,react,webpack
函數式組件很是的簡潔純粹,一個函數既是一個組件。

  • 函數組件沒有生命週期
  • 也沒有它本身的內部狀態
  • 由於只是一個函數,在調用時沒有內部的 this

上圖是一個很簡單的函數組件例子,雖說函數組件本質上就是 一個 JavaScript 函數, 在例子裏看起來沒有任何使用react的api或者方法,可是咱們仍然引入了react,這是由於在組件內部返回的react 元素 使用了 jsx,他實質上是React.createElement的語法糖,配置好babel就能夠爲咱們編譯jsx,簡化了咱們寫createElement的過程。react

Stateless Component

binlive前端開發,web開發,node,vue,react,webpack
函數式組件在以往咱們也稱其爲無狀態組件(Stateless Component)。函數式組件中並不須要實例化並且也沒有生命週期與本身內部的狀態管理,它只簡單的接受props而後進行渲染。在處理一些簡單的沒有UI展現性內容時使用無狀態組件能更好的進行性能優化。

Class 與 Functional 差別

binlive前端開發,web開發,node,vue,react,webpack

函數組件不須要聲明類,能夠避免大量的譬如extends或者constructor這樣的代碼 也不須要處理 this 指向的問題。 更加純粹的是一個函數就是一個組件,React 官方說的 React 組件一直更像是函數,咱們寫函數式組件彷佛也是更加貼近react的原則。 引用透明性是函數式編程的一個概念,我我的以爲函數組件遵循了純函數的概念。webpack

純函數

binlive前端開發,web開發,node,vue,react,webpack

引用透明性是函數式編程裏的概念。 這是一個 redux 裏的 reducer 函數。在 redux 裏 reducer 須要被定義爲一個純函數,它符合純函數的幾個定義:git

  1. 相同的輸入總會有相同的輸出
  2. 不會修改函數的輸入值 reducer不可以修改state,只能返回一個新的state
  3. 不依賴外部環境狀態
  4. 無任何反作用

React Hook

不少時候一開始咱們寫的代碼或者組件都是比較簡單的,咱們可能會選 函數組件來完成一個 簡單的功能模塊。可是越到後面可能功能就變的越發的複雜了,函數組件內可能須要一些本身的狀態或者生命週期了。 這時候想要實現這些功能可能就須要把它藉助 高階組件 或者 render props 幫它包裝一層class 的父組件,這樣它就間接的擁有了狀態跟生命週期。可是這也都只是在函數組件外借助了 class 組件的能力。es6

因爲函數組件在每次渲染時候,組件內部都會從上到下從新執行一遍,它也沒有辦法有本身內部的狀態,也沒法產生反作用,爲了加強函數組件,hook就出現了。github

Vue Hook

binlive前端開發,web開發,node,vue,react,webpack

Vue 在放出可能到來 3.0 更新的內容,上圖就是此次更新變更比較大的 funtion based api.web

對比右側2.0的寫法, template 就是 以前的模板語法。比較特殊的就是 setup 函數。 這裏的value是一個包裝對象,它就是組件內部的狀態,這個看起來跟react 的 usestate hook 仍是很是像的。 onMounted 對應的就是之前的生命週期函數 mounted,方法最後的返回對象有點像以前的data方法裏的內容被綁定到了template模板語法上,setup 看起來就是一個簡單的函數。typescript

binlive前端開發,web開發,node,vue,react,webpack
vue做者提到了 function based api 借鑑了 react hook 的思想。 因爲 typescript對函數的類型推導能力比較好,以前vue組件比較流行的那種對象的寫法如今看起來也變成了setup這樣一個函數了。 至於更靈活的邏輯複用能力,這個在後面會看到是如何進行邏輯複用的。

React Hook

binlive前端開發,web開發,node,vue,react,webpack
Hook 是 React 16.8 的新增特性。它是在函數組件的基礎上引入了一些新API,可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性。

咱們看到這裏使用了一個 useState 的API,它能夠在調用時傳入參數,而且返回一個數組。數組中有兩項內容,第一個是狀態(state)、第二個是設置狀態(setState),使用es6 數組解構的特性,咱們能夠根據咱們的須要去語義化命名,useState中的init參數只會在方法在第一次初始化之後,即便函數組件被更新,它的state也不會被重置,因此它能夠一直保留着當前的狀態。 這也就讓函數組件擁有了內部狀態,以及生命週期。

一些hook用法以及注意點

hook跳過state更新

binlive前端開發,web開發,node,vue,react,webpack

在react 的 class 組件中,咱們會定義一個數組類型的 state,在對修改了引用中data裏的一些值後,調用this.setState 方法會觸發從新渲染,可是在一樣的操做在hook裏並不會觸發更新。

binlive前端開發,web開發,node,vue,react,webpack
調用咱們設置 hook 的 setData 更新函數,數並傳入當修改過引用類型的data 去觸發更新時,React將跳過子組件的渲染及 effect 的執行。(React 使用 Object.is 比較先後 state)因爲引用地址沒變化 因此不會發生從新渲染。 咱們可使用圖中下面的兩種方式淺拷貝對象 而後再進行操做,便可解決這一問題。

useEffect介紹

binlive前端開發,web開發,node,vue,react,webpack
useEffect 正如它的名字同樣,他能夠在函數組件中使用一些反作用,咱們能夠用來模擬一些生命週期操做。 useEffect 擁有兩個參數,第一個參數是一個回調函數,它會在完成一些狀態更新以及組件渲染後被觸發,第二個參數是一個數組是一個可選的參數.

useEffect的規則以下:

  1. 當第二個不存在時,在第一次初始化和每次從新渲染後都會觸發回調。
  2. 當數組存在並有值時,若是數組中的任何值發生更改,則每次渲染後都會觸發回調。
  3. 當它是一個空數組時,回調只會被觸發一次,相似於 componentDidMount。
  4. 每一個 useEffect 均可以返回一個清除函數。

Hooks Rules

binlive前端開發,web開發,node,vue,react,webpack
React 官方文檔說明,不要在循環,條件或嵌套函數中調用 Hook,只在最頂層使用 Hook。 只在最頂層使用Hook並非說要將 hooks 代碼寫在組件的最上面,而是hooks的代碼不能套在一些判斷或者循環條件中。這些規範要求最終的目的都是要保證當組件建立跟後續的更新時,每次的 hoos 順序都是一致的。

爲何Hooks 須要遵循規則

binlive前端開發,web開發,node,vue,react,webpack
這是react源碼中對於Hook的類型聲明。

  • memoizedState: 存儲的上一次所有update隊列執行完成以後的狀態值
  • baseState: 初始化 initialState 值, 以及每次 調用setter 觸發 dispatch 以後 更新的 newState
  • baseUpdate:當前須要更新的 Update
  • Queue: 當前hook的更新隊列,它會存儲屢次更新行爲,咱們每次 setter 都會 dispatch都會被推到這個隊列中

binlive前端開發,web開發,node,vue,react,webpack
咱們依次調用三個 useState hook

React內部會建立對應的 hook 節點,react 第一次初始化組件時內部會將這些節點結構經過 next 依次鏈接起來,造成一個單向鏈表結構,每一次調用setter 就 dispach 一個對應的action方法,將action存儲在queue中。

在後續觸發渲染時,react 會調用 queue 隊列中的內容,queue 也是一個鏈表結構,它是收集咱們調用setter方法,queue 會依次執行內部的action,從而獲取到最新的狀態值,狀態值會被更新到memorizedState上,而後將狀態反應到對應的hook字段中造成綁定。因此當咱們打亂或者增長減小hooks時,會形成hook內部順序錯誤,從而致使沒法正常工做。

binlive前端開發,web開發,node,vue,react,webpack

上圖是組合成的一個hooks單向鏈表結構,內部依次經過next指向下一個hook。

嘗試經過修改hook數量,在第一次初始化時候咱們調用了三個usestate hook,react也會在它內部依次收集了三個hook。 第一次渲染時,頁面能夠正常的渲染出內容,當咱們點擊了一個 setter 方法,從新觸發了渲染時,會因爲 hook 數量比以前的少而致使頁面錯誤,沒法正常渲染。因此保證每次都是相同的調用順序才能正確的使用hook。

binlive前端開發,web開發,node,vue,react,webpack
Hooks API是專門爲函數組件設計的,因此咱們只能在函數式組件內部或者自定義的hook中使用hooks,包括usestate、useeffect、userref 等以及自定義的hook api。並且自定義hook,也是沒法在class組件內部使用的。 固然正常的函數組件,即便在內部使用了hook,咱們能夠也class組件內部複用該函數組件,這個是不會影響的。

Hook 生命週期

binlive前端開發,web開發,node,vue,react,webpack
Constructor: 咱們之前在constructor裏更多的是用來初始化state,在使用hook建立state時能夠傳入 initialState 初始化便可, 若是你有一些複雜的計算邏輯,初始化的參數還能夠是一個函數,只要返回計算後的值便可

componentDidMount: 當第二個參數爲空數組時,回調只會被觸發一次,相似於componentDidMount。

binlive前端開發,web開發,node,vue,react,webpack
componentDidUpdate: 當 useEffect 不傳入第二個參數時,在第一次初始化和每次從新渲染後都會觸發回調。這與 componentDidUpdate 有些不一樣,componentDidUpdate 生命週期在初始化掛載的時候不會被調用,咱們可使用 useRef 鉤子來存儲一個值來判斷函數是否爲第一次調用

useRef返回一個可變的ref對象,它不只能夠綁定dom的引用,也能夠用來存儲一些值。其.current屬性被初始化爲傳遞的參數(initialValue)。返回的對象將持續整個組件的生命週期。變動 .current 屬性不會引起組件從新渲染。

componentWillUnmount 前面提到每一個 useEffect 均可以返回一個清除函數。配合這一特性,咱們能夠任然將第二個參數設置爲一個空數組,這樣清除函數會在組件卸載前被調用,咱們能夠在這裏面清除一些事件監聽器等。更方便的是咱們能夠將 didMount 和 unMount 寫在同一個 useEffect 鉤子中。

binlive前端開發,web開發,node,vue,react,webpack
componentWillReceiveProp: 咱們想要模擬以前的 componentWillReceiveProps 生命函數,能夠將第二個參數設爲須要觀察的props參數。爲了比對先後的 props 變化,能夠用 useRef 來存儲舊的props來達到目的。

setState的第二個參數 咱們知道在使用setState方法時,能夠傳入第二個參數,這個參數是個回調函數,它在設置完 state 之後 會被調用。 咱們在hook裏也能夠經過用useEffect 鉤子傳入要監聽的 state,當該state更新之後,回調函數會被調用。

邏輯複用

binlive前端開發,web開發,node,vue,react,webpack
在vue和react官方都提到了hook能夠更好的邏輯複用,他們的動機都是由於組件間一些重複的邏輯代碼沒法複用,咱們看下如何使用react hooks完成邏輯代碼的複用。

自定義Hook

binlive前端開發,web開發,node,vue,react,webpack
這是一個自定義 hook,也就是咱們本身建立一個hook。

React 規定 自定義hook 須要用 use 開頭來定義,自定義hook雖然不是一個組件,它不須要返回 React Node 元素節點,若是返回了元素,它就又變成的一個函數組件,可是雖然這樣,它一樣的能夠在方法內部使用 狀態 usestate 和生命週期 useeffect 等其餘的 hooks。他擁有本身的內部狀態和生命週期。

比較特殊的是 自定義hook 返回的內容能夠任何類型,你能夠單純的返回一個值,也能夠返回一個對象或者數組甚至是方法,返回的內容取決於在調用你建立的自定義hook時所須要的一些屬性。

用法說明:

咱們在這裏定義了一個名爲 useOnline 的自定義hook, 咱們用它來判斷當前的網絡鏈接狀態。首先hook的方法名是以use開頭的,後面的名字能夠根據你的意願來定義,大部分時候hook都會遵循駝峯命名法,只要是以use開頭,後面的定義是沒有限制的。不過我嘗試了一下即便不使用use開頭建立的自定義hook其實也是正常運行的。react 約定以use開頭是爲了經過一些自動檢查工具來來校驗 這些use開頭的hook內部是否違反了 hook的使用規則。不過仍是最好遵循使用use開頭,駝峯命名這樣一個約定。

而後調用 usestate 建立一個 自定義hook內部的狀態, 傳入了 navigator online 初始化當前的網絡狀態,而且在自定義 hook 內部擁有了本身的狀態。

接着在使用 useeffect 鉤子內,咱們須要監聽網絡狀態的變化,聯網或者斷網的監聽方法只須要在組件建立時調用一次便可,因此咱們在傳遞useeffect的第二個參數時,只傳遞了一個空的數組,這樣它就只會在 didmount 時被調用一次。

一樣的在銷燬組件時,咱們也須要清除掉這些監聽事情。在 effect 鉤子的返回函數中清理掉這兩個監聽方法便可

最後咱們將 當前的狀態值 做爲自定義hook的返回值,這樣就完成了一個監聽網絡狀態的自定義hook。

調用 useOnline 自定義hook

binlive前端開發,web開發,node,vue,react,webpack

咱們在一個最簡單的函數組件中引入這個hook。 而後在函數組件內咱們調用這個自定義hook,hook會返回當前的網絡狀態,這是一個很簡單的函數組件,咱們只渲染當前的網絡狀態。

當咱們渲染出組件時候他渲染出了當前的網絡狀態, 當咱們切換爲離線狀態時,能夠看組件被從新渲染出了當前的網絡狀態值。這樣,當咱們建立出一個自定義hook時 就能夠在不少地方調用直接複用這段代碼,達到了邏輯複用的目的。

不過你們可能以爲這不就是抽象一個方法麼? 好像沒有自定義hook 咱們也能夠把一些方法抽象到一些工具函數中, 寫一些 helper 或者 utils來完成這些功能 。 可是實際上是不同的,自定義的hook強大之處在於擁有狀態,當狀態值改變時,它能夠觸發調用組件的從新渲染,並且自定義hook能夠跟隨着組件的生命週期,在不一樣的生命鉤子階段,咱們能夠處理一些事件。

自定義生命週期Hook

binlive前端開發,web開發,node,vue,react,webpack
咱們在以前也用hook的useeffect API 實現過模擬 class 組件中的生命週期,可是若是函數組件多了,咱們可能在不少組件中都會用到這些生命週期,雖然使用 useeffect 實現幾個生命週期也只有幾行代碼,可是在每一個組件裏都寫一遍顯然不符合邏輯複用的原則。

咱們能夠選擇本身去 實現 一個 useDidMount 自定義hook,實現起來也很是簡單。只須要在鉤子內部調用 useeffect hook,將它的第一個參數傳遞爲咱們自定義鉤子的回調函數參數,將第二個可選的參數設爲一個空數組,這樣就只會在 didmount時候被調用一次。

開源社區裏的自定義Hook

binlive前端開發,web開發,node,vue,react,webpack
這是github上star較多的一個 use hook 項目, 叫 react-use,該項目到目前爲止已經有 8.7K的star。

這個開源項目幫咱們收集了一系列已經封裝好的自定義hook,好比這些生命週期鉤子,咱們只須要引入便可實現這些生命週期。包括一些 didMount, unmount, update 等鉤子。

使用React Hook寫的進度條組件

binlive前端開發,web開發,node,vue,react,webpack

分享一個React Hook弧形進度條組件react-arc-progress,這個以前是一個es6方式寫的插件,而後以前爲了跟進 react hook,將它改形成了一個 react 組件。

  1. 由於使用了 react hooks 的api,因此只能使用在 react 版本 >= 16.8.0 版本
  2. 使用的typescript,若是一樣使用了ts時,使用組件對參數以及方法會自動有類型推導提示
  3. 源碼使用了tslint 以及 愛彼迎規範 進行了行代碼風格校驗

重複的造輪子確定是沒有意義的,因此要添加一些獨特的功能讓它變得更不同些:

  • 大部分圓環進度條都是一個整圓,這個組件是能夠是弧形的,能夠自由定義他的起始結束位置。
  • 能夠傳入一個數值,它會隨着進度條的增加自動增長。
  • 能夠傳入一個或多個文字節點
  • 能夠定義進度條的厚度,讓內層超過外層
  • 進度條背景能夠貼圖
  • 動畫速度能夠經過一個速度閥值去調整,也能夠固定一個動畫時長

GitHub地址,請給個star吧 ◔ ‸◔

相關文章
相關標籤/搜索