相信大部分人都已經在使用 React hooks 了,可是在開發過程當中,咱們要 知其然知其因此然。整理了一下最近使用 React hooks 遇到的一些問題,若是有問題或更好的答案,歡迎一塊兒交流。html
沒有hooks以前使用 render props
和 高階組件
。render props
是接受一個組件做爲props
, HOC
是一個函數,接受一個組件做爲參數,返回另外一個組件。使用這些開發的組件會造成「嵌套地獄」,調試困難。前端
不少組件在最開始寫的時候都是很簡單的,基本上就是隻作一件事,當你的業務邏輯變得複雜以後,組件也會變得複雜起來。大多數狀況下,咱們不大可能把組件拆分的更小,由於可能有不少共用的狀態邏輯,拆分後,組件之間的通訊也會很複雜,甚至須要引用 Redux 來管理組件之間的狀態。vue
要想用好 class 組件,你必須瞭解 ES6 中的class,理解 JavaSript 中 this
的工做方式,要注意綁定事件處理器,清楚當前this的指向。react
詳細查看react 官方文檔 Hook 簡介
咱們在日常開發中常常會遇到不少的頁面有一些公共的邏輯,咱們不能每次遇到的時候都直接把原來的代碼 copy 過來改扒改扒,改的時候又要全局搜索改掉(很難保證沒有漏的,費時費力)因此要想辦法去複用,mixin
、HOC
, render props
等都是實現邏輯複用的方式。git
vue和react中都曾用過mixin(react目前已經拋棄)
mixin(混入)本質上就是將對象複製到另外一個對象上。github
const mixin = function (obj, mixins) { const newObj = obj; newObj.prototype = Object.create(obj.prototype); for(let prop in mixins) { if(mixins.hasOwnProperty(prop)) { newObj.prototype[prop] = mixins[prop]; } } return newObj; } const obj = { sayHello() { console.log('hello'); } }; const otherObj = function() { console.log('otherObj'); } const Obj = mixin(otherObj, obj); const a = new Obj(); // otherObj a.sayHello(); // hello
mixin存在的幾個問題:web
HOC是React社區提出的新的方式用來取代mixin的。
高階函數是函數式編程中一個基本的概念,它描述了一種這樣的函數:接受函數做爲輸入,或是返回一個函數,好比 map, reduce等都是高階函數。
高階組件( higher-order component),相似於高階組件接受一個組件做爲參數,返回另外一個組件。面試
function getComponent(WrappedComponent) { return class extends React.Component { render() { return <WrappedComponent {...this.props}/>; } }; }
HOC的優勢爲:編程
HOC的問題是:api
render props: 經過props接受一個返回react element 的函數,來動態決定本身要渲染的結果
<DataProvider render={data => ( <h1>Hello {data.target}</h1> )}/>
React Router中就用到了 Render Props
<Router> <Route path="/home" render={() => <div>Home</div>} /> </Router>,
它有哪些問題呢
具體實現就是經過一個函數來封裝跟狀態有關的邏輯,將這些邏輯從組件中抽取出來。而這個函數中咱們可使用其餘的Hooks,也能夠單獨進行測試,甚至將它貢獻給社區。
import { useState, useEffect } from 'react'; function useCount() { const [count, setCount] = useState(0); useEffect(() = { document.title = `You clicked ${count} times`; }); return count }
hooks的引入就是爲了解決上面提到的這麼問題,由於 使用函數式組件,咱們在開發組件的時候,能夠當作日常寫函數同樣自由。
函數複用是比較容易的,直接傳不一樣的參數就能夠渲染不一樣的組件,複雜組件實現,咱們徹底能夠多封裝幾個函數,每一個函數只單純的負責一件事。並且不少公用的代碼邏輯和一些場景咱們能夠抽出來,封裝成自定義hooks使用,好比 Umi Hooks庫封裝了不少共用的邏輯,好比 useSearch,封裝了異步搜索場景的邏輯;好比 useVirtualList,就封裝了虛擬列表的邏輯。
在使用hooks的時候,你可能會對它的規則有不少疑問,好比:
...
咱們先來看一下官方文檔給出的解釋
每一個組件內部都有一個「記憶單元格」列表。它們只不過是咱們用來存儲一些數據的 JavaScript 對象。當你用 useState() 調用一個 Hook 的時候,它會讀取當前的單元格(或在首次渲染時將其初始化),而後把指針移動到下一個。這就是多個 useState() 調用會獲得各自獨立的本地 state 的緣由。
React中是經過相似單鏈表的形式來實現的,經過 next 按順序串聯全部的 hook。能夠看下 源碼部分
export type Hook = {| memoizedState: any, baseState: any, baseQueue: Update<any, any> | null, queue: UpdateQueue<any, any> | null, next: Hook | null, |}; export type Effect = {| tag: HookEffectTag, create: () => (() => void) | void, destroy: (() => void) | void, deps: Array<mixed> | null, next: Effect, |};
更詳細的推薦查看 React Hooks 原理 和 Under the hood of React’s hooks system。
咱們先來看一下使用 setState 的更新機制:
在React
的setState
函數實現中,會根據一個變量isBatchingUpdates
判斷是直接更新this.state
仍是放到 隊列中回頭再說。而isBatchingUpdates
默認是false
,也就表示setState
會同步更新this.state
。可是,有一個函數 batchedUpdates
, 這個函數會把isBatchingUpdates
修改成true
,而當React
在調用事件處理函數以前就會調用這個batchedUpdates
,形成的後果,就是由React控制的事件處理程序過程setState
不會同步更新this.state
。
知道這些,咱們下面來看兩個例子。
下面的代碼輸出什麼?
class Example extends React.Component { constructor() { super(); this.state = { val: 0 }; } componentDidMount() { this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 1 次 log this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 2 次 log setTimeout(() => { this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 3 次 log 1 this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 4 次 log 2 }, 0); } render() { return null; } };
打印結果是: 0, 0, 2, 3。
dirtyComponents
,因此打印時獲取的都是更新前的狀態 0this.state.val
都是 0,因此執行時都是將0設置爲1,在react內部會被合併掉,只執行一次。設置完成後 state.val
值爲1。isBatchingUpdates爲false
,因此可以直接進行更新,因此連着輸出 2, 3上面代碼改用react hooks的話
import React, { useEffect, useState } from 'react'; const MyComponent = () => { const [val, setVal] = useState(0); useEffect(() => { setVal(val+1); console.log(val); setVal(val+1); console.log(val); setTimeout(() => { setVal(val+1); console.log(val); setVal(val+1); console.log(val); }, 0) }, []); return null }; export default MyComponent;
打印輸出: 0, 0, 0, 0。
更新的方式沒有改變。首先是由於 useEffect
函數只運行一次,其次setTimeout
是個閉包,內部獲取到值val一直都是 初始化聲明的那個值,因此訪問到的值一直是0。以例子來看的話,並無執行更新的操做。
在這種狀況下,須要使用一個容器,你能夠將更新後的狀態值寫入其中,並在之後的 setTimeout
中訪問它,這是useRef
的一種用例。能夠將狀態值與ref
的current
屬性同步,並在setTimeout
中讀取當前值。
關於這部分詳細內容能夠查看 React useEffect的陷阱