您好,登錄后才能下訂單哦!
Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性
從官網的這句話中,我們可以明確的知道,Hook增加了函數式組件中state的使用,在之前函數式組件是無法擁有自己的狀態,只能通過props以及context來渲染自己的UI,而在業務邏輯中,有些場景必須要使用到state,那么我們就只能將函數式組件定義為class組件。而現在通過Hook,我們可以輕松的在函數式組件中維護我們的狀態,不需要更改為class組件。
React Hooks要解決的問題是狀態共享,這里的狀態共享是指只共享狀態邏輯復用,并不是指數據之間的共享。我們知道在React Hooks之前,解決狀態邏輯復用問題,我們通常使用higher-order components和render-props,那么既然已經有了這兩種解決方案,為什么React開發者還要引入React Hook?對于higher-order components和render-props,React Hook的優勢在哪?
React Hook例子
我們先來看一下React官方給出的React Hook的demo
import?{?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> ?); } 復制代碼
我們再來看看不用React Hook的話,如何實現
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> ?); ?} } 復制代碼
可以看到,在React Hook中,class Example組件變成了函數式組件,但是這個函數式組件卻擁有的自己的狀態,同時還可以更新自身的狀態。這一切都得益于useState這個Hook,useState 會返回一對值:當前狀態和一個讓你更新它的函數,你可以在事件處理函數中或其他一些地方調用這個函數。它類似 class 組件的 this.setState,但是它不會把新的 state 和舊的 state 進行合并
React復用狀態邏輯的解決方案
Hook是另一種復用狀態邏輯的解決方案,React開發者一直以來對狀態邏輯的復用方案不斷提出以及改進,從Mixin到高階組件到Render Props 到現在的Hook,我們先來簡單了解一下以前的解決方案
Mixin模式
在React最早期,提出了根據Mixin模式來復用組件之間的邏輯。在Javascript中,我們可以將Mixin繼承看作是通過擴展收集功能的一種途徑.我們定義的每一個新的對象都有一個原型,從中它可以繼承更多的屬性.原型可以從其他對象繼承而來,但是更重要的是,能夠為任意數量的對象定義屬性.我們可以利用這一事實來促進功能重用。
React中的mixin主要是用于在完全不相關的兩個組件中,有一套基本相似的功能,我們就可以將其提取出來,通過mixin的方式注入,從而實現代碼的復用。例如,在不同的組件中,組件需要每隔一段時間更新一次,我們可以通過創建setInterval()函數來實現這個功能,同時在組件銷毀的時候,我們需要卸載此函數。因此可以創建一個簡單的 mixin,提供一個簡單的 setInterval() 函數,它會在組件被銷毀時被自動清理。
var?SetIntervalMixin?=?{ ?componentWillMount:?function()?{ ?this.intervals?=?[]; ?}, ?setInterval:?function()?{ ?this.intervals.push(setInterval.apply(null,?arguments)); ?}, ?componentWillUnmount:?function()?{ ?this.intervals.forEach(clearInterval); ?} }; var?createReactClass?=?require('create-React-class'); var?TickTock?=?createReactClass({ ?mixins:?[SetIntervalMixin],?//?使用?mixin ?getInitialState:?function()?{ ?return?{seconds:?0}; ?}, ?componentDidMount:?function()?{ ?this.setInterval(this.tick,?1000);?//?調用?mixin?上的方法 ?}, ?tick:?function()?{ ?this.setState({seconds:?this.state.seconds?+?1}); ?}, ?render:?function()?{ ?return?( ?<p> ?React?has?been?running?for?{this.state.seconds}?seconds. ?</p> ?); ?} }); ReactDOM.render( ?<TickTock?/>, ?document.getElementById('example') ); 復制代碼
mixin的缺點
不同mixin可能會相互依賴,耦合性太強,導致后期維護成本過高
mixin中的命名可能會沖突,無法使用同一命名的mixin
mixin即使開始很簡單,它們會隨著業務場景增多,時間的推移產生滾雪球式的復雜化
具體缺點可以看此鏈接Mixins是一種禍害
因為mixin的這些缺點存在,在React中已經不建議使用mixin模式來復用代碼,React全面推薦使用高階組件來替代mixin模式,同時ES6本身是不包含任何 mixin 支持。因此,當你在 React 中使用 ES6 class 時,將不支持 mixins 。
高階組件
高階組件(HOC)是 React 中用于復用組件邏輯的一種高級技巧。HOC 自身不是 React API 的一部分,它是一種基于 React 的組合特性而形成的設計模式
高級組件并不是React提供的API,而是React的一種運用技巧,高階組件可以看做是裝飾者模式(Decorator Pattern)在React的實現。裝飾者模式: 動態將職責附加到對象上,若要擴展功能,裝飾者提供了比繼承更具彈性的代替方案.
具體而言,高階組件是參數為組件,返回值為新組件的函數。
組件是將 props 轉換為 UI,而高階組件是將組件轉換為另一個組件
我們可以通過高階組件動態給其他組件增加日志打印功能,而不影響原先組件的功能
function?logProps(WrappedComponent)?{ ?return?class?extends?React.Component?{ ?componentWillReceiveProps(nextProps)?{ ?console.log('Current?props:?',?this.props); ?console.log('Next?props:?',?nextProps); ?} ?render()?{ ?return?<WrappedComponent?{...this.props}?/>; ?} ?} } 復制代碼
Render Propss
術語 “Render Props” 是指一種在 React 組件之間使用一個值為函數的 prop 共享代碼的簡單技術
具有 Render Props 的組件接受一個函數,該函數返回一個 React 元素并調用它而不是實現自己的渲染邏輯
以下我們提供了一個帶有prop的<Mouse>組件,它能夠動態決定什么需要渲染,這樣就能對<Mouse>組件的邏輯以及狀態復用,而不用改變它的渲染結構。
class?Cat?extends?React.Component?{ ?render()?{ ?const?mouse?=?this.props.mouse; ?return?( ?<img?src="/cat.jpg"?style={{?position:?'absolute',?left:?mouse.x,?top:?mouse.y?}}?/> ?); ?} } class?Mouse?extends?React.Component?{ ?constructor(props)?{ ?super(props); ?this.handleMouseMove?=?this.handleMouseMove.bind(this); ?this.state?=?{?x:?0,?y:?0?}; ?} ?handleMouseMove(event)?{ ?this.setState({ ?x:?event.clientX, ?y:?event.clientY ?}); ?} ?render()?{ ?return?( ?<div?style={{?height:?'100%'?}}?onMouseMove={this.handleMouseMove}> ?{this.props.render(this.state)} ?</div> ?); ?} } class?MouseTracker?extends?React.Component?{ ?render()?{ ?return?( ?<div> ?<h2>移動鼠標!</h2> ?<Mouse?render={mouse?=>?( ? ?)}/> ?</div> ?); ?} } 復制代碼
然而通常我們說的Render Props 是因為模式才被稱為 Render Props ,又不是因為一定要用render對prop進行命名。我們也可以這樣來表示
<Mouse> ?{mouse?=>?( ?<Cat?mouse={mouse}?/> ?)} </Mouse> 復制代碼
React Hook動機
React Hook是官網提出的又一種全新的解決方案,在了解React Hook之前,我們先看一下React Hook提出的動機
在組件之間復用狀態邏輯很難
復雜組件變得難以理解
難以理解的 class
下面說說我對這三個動機的理解:
在組件之間復用狀態邏輯很難,在之前,我們通過高階組件(Higher-Order Components)和渲染屬性(Render Propss)來解決狀態邏輯復用困難的問題。很多庫都使用這些模式來復用狀態邏輯,比如我們常用redux、React Router。高階組件、渲染屬性都是通過組合來一層層的嵌套共用組件,這會大大增加我們代碼的層級關系,導致層級的嵌套過于夸張。從React的devtool我們可以清楚的看到,使用這兩種模式導致的層級嵌套程度
復雜組件變得難以理解,在不斷變化的業務需求中,組件逐漸會被狀態邏輯以及副作用充斥,每個生命周期常常會包含一些不相關的邏輯。我們寫代碼通常都依據函數的單一原則,一個函數一般只處理一件事,但在生命周期鉤子函數中通常會同時做很多事情。比如,在我們需要在componentDidMount中發起ajax請求獲取數據,同時有時候也會把事件綁定寫在此生命周期中,甚至有時候需要在componentWillReceiveProps中對數據進行跟componentDidMount一樣的處理。
相互關聯且需要對照修改的代碼被進行了拆分,而完全不相關的代碼卻在同一個方法中組合在一起。如此很容易產生 bug,并且導致邏輯不一致。
難以理解的class,個人覺得使用class組件這種還是可以的,只要了解了class的this指向綁定問題,其實上手的難度不大。大家要理解,這并不是 React 特有的行為;這其實與 JavaScript 函數工作原理有關。所以只要了解好JS函數工作原理,其實this綁定都不是事。只是有時候為了保證this的指向正確,我們通常會寫很多代碼來綁定this,如果忘記綁定的話,就有會各種bug。綁定this方法:
1.this.handleClick?=?this.handleClick.bind(this); 2.<button?onClick={(e)?=>?this.handleClick(e)}> ?Click?me ?</button> 復制代碼
于是為了解決以上問題,React Hook就被提出來了
state Hook使用
我們回到剛剛的代碼中,看一下如何在函數式組件中定義state
import?React,?{?useState?}?from?'React'; const?[count,?setCount]?=?useState(0); 復制代碼
useState做了啥
我們可以看到,在此函數中,我們通過useState定義了一個'state變量',它與 class 里面的 this.state 提供的功能完全相同.相當于以下代碼
class?Example?extends?React.Component?{ ?constructor(props)?{ ?super(props); ?this.state?=?{ ?count:?0 ?}; ?} 復制代碼
useState參數
在代碼中,我們傳入了0作為useState的參數,這個參數的數值會被當成count初始值。當然此參數不限于傳遞數字以及字符串,可以傳入一個對象當成初始的state。如果state需要儲存多個變量的值,那么調用多次useState即可
useState返回值
返回值為:當前 state 以及更新 state 的函數,這與 class 里面 this.state.count 和 this.setState 類似,唯一區別就是你需要成對的獲取它們。看到[count, setCount]很容易就能明白這是ES6的解構數組的寫法。相當于以下代碼
let?_useState?=?useState(0);//?返回一個有兩個元素的數組 let?count?=?_useState[0];//?數組里的第一個值 let?setCount?=?_useState[1];//?數組里的第二個值 復制代碼
讀取狀態值
只需要使用變量即可
以前寫法
<p>You?clicked?{this.state.count}?times</p> 復制代碼
現在寫法
<p>You?clicked?{count}?times</p> 復制代碼
更新狀態
通過setCount函數更新
以前寫法
<button?onClick={()?=>?this.setState({?count:?this.state.count?+?1?})}> ?Click?me ?</button> 復制代碼
現在寫法
?<button?onClick={()?=>?setCount(count?+?1)}> ?Click?me ?</button> 復制代碼
這里setCount接收的參數是修改過的新狀態值
聲明多個state變量
我們可以在一個組件中多次使用state Hook來聲明多個state變量
function?ExampleWithManyStates()?{ ?//?聲明多個?state?變量! ?const?[age,?setAge]?=?useState(42); ?const?[fruit,?setFruit]?=?useState('banana'); ?const?[todos,?setTodos]?=?useState([{?text:?'Learn?Hooks'?}]); ?//?... } 復制代碼
React 假設當你多次調用 useState 的時候,你能保證每次渲染時它們的調用順序是不變的
為什么React要規定每次渲染它們時的調用順序不變呢,這個是一個理解Hook至關重要的問題
Hook 規則
Hook 本質就是 JavaScript 函數,但是在使用它時需要遵循兩條規則。并且React要求強制執行這兩條規則,不然就會出現異常的bug
只在最頂層使用 Hook
不要在循環,條件或嵌套函數中調用 Hook,?確保總是在你的 React 函數的最頂層調用他們
只在 React 函數中調用 Hook
不要在普通的 JavaScript 函數中調用 Hook
這兩條規則出現的原因是,我們可以在單個組件中使用多個State Hook 或 Effect Hook,React 靠的是 Hook 調用的順序來知道哪個 state 對應哪個useState
function?Form()?{ ?const?[name1,?setName1]?=?useState('Arzh2'); ?const?[name2,?setName2]?=?useState('Arzh3'); ?const?[name3,?setName3]?=?useState('Arzh4'); ?//?... } //?------------ //?首次渲染 //?------------ useState('Arzh2')?//?1.?使用?'Arzh2'?初始化變量名為?name1?的?state useState('Arzh3')?//?2.?使用?'Arzh3'?初始化變量名為?name2?的?state useEffect('Arzh4')? //?3.?使用?'Arzh4'?初始化變量名為?name3?的?state //?------------- //?二次渲染 //?------------- useState('Arzh2')?//?1.?讀取變量名為?name1?的?state(參數被忽略) useState('Arzh3')?//?2.?讀取變量名為?name2?的?state(參數被忽略) useEffect('Arzh4')?//?3.?讀取變量名為?name3?的?state(參數被忽略) 復制代碼
如果我們違反React的規則,使用條件渲染
if?(name?!==?'')?{ ?const?[name2,?setName2]?=?useState('Arzh3'); } 復制代碼
假設第一次(name !== '')為true的時候,執行此Hook,第二次渲染(name !== '')為false時,不執行此Hook,那么Hook的調用順序就會發生變化,產生bug
useState('Arzh2')?//?1.?讀取變量名為?name1?的?state //useState('Arzh3')?//?2.?Hook被忽略 useEffect('Arzh4')?//?3.?讀取變量名為?name2(之前為name3)?的?state 復制代碼
React 不知道第二個 useState 的 Hook 應該返回什么。React 會以為在該組件中第二個 Hook 的調用像上次的渲染一樣,對應的是 arzh3 的 useState,但并非如此。所以這就是為什么React強制要求Hook使用必須遵循這兩個規則,同時我們可以使用 eslint-plugin-React-Hooks來強制約束
Effect Hook使用
我們在上面的代碼中增加Effect Hook的使用,在函數式組件中增加副作用,修改網頁的標題
?useEffect(()?=>?{ ?document.title?=?`You?clicked?${count}?times`; ?}); 復制代碼
如果你熟悉 React class 的生命周期函數,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 這三個函數的組合。
也就是我們完全可以通過useEffect來替代這三個生命鉤子函數
我們來了解一下通常需要副作用的場景,比如發送請求,手動變更dom,記錄日志等。通常我們都會在第一次dom渲染完成以及后續dom重新更新時,去調用我們的副作用操作。我們可以看一下以前生命周期的實現
?componentDidMount()?{ ?document.title?=?`You?clicked?${this.state.count}?times`; ?} ?componentDidUpdate()?{ ?document.title?=?`You?clicked?${this.state.count}?times`; ?} 復制代碼
這也就是我們上面提到的React Hook動機的第二個問題來源之一,需要在第一次渲染以及后續的渲染中調用相同的代碼
Effect在默認情況下,會在第一次渲染之后和每次更新之后都會執行,這也就讓我們不需要再去考慮是componentDidMount還是componentDidUpdate時執行,只需要明白Effect在組件渲染后執行即可
清除副作用
有時候對于一些副作用,我們是需要去清除的,比如我們有個需求需要輪詢向服務器請求最新狀態,那么我們就需要在卸載的時候,清理掉輪詢的操作。
?componentDidMount()?{ ?this.pollingNewStatus() ?} ?componentWillUnmount()?{ ?this.unPollingNewStatus() ?} 復制代碼
我們可以使用Effect來清除這些副作用,只需要在Effect中返回一個函數即可
?useEffect(()?=>?{ ?pollingNewStatus() ?//告訴React在每次渲染之前都先執行cleanup() ?return?function?cleanup()?{ ?unPollingNewStatus() ?}; ?}); 復制代碼
有個明顯的區別在于useEffect其實是每次渲染之前都會去執行cleanup(),而componentWillUnmount只會執行一次。
Effect性能優化
useEffect其實是每次更新都會執行,在某些情況下會導致性能問題。那么我們可以通過跳過 Effect 進行性能優化。在class組件中,我們可以通過在 componentDidUpdate 中添加對 prevProps 或 prevState 的比較邏輯解決
componentDidUpdate(prevProps,?prevState)?{ ?if?(prevState.count?!==?this.state.count)?{ ?document.title?=?`You?clicked?${this.state.count}?times`; ?} } 復制代碼
在Effect中,我們可以通過增加Effect的第二個參數即可,如果沒有變化,則跳過更新
useEffect(()?=>?{ ?document.title?=?`You?clicked?${count}?times`; },?[count]);?//?僅在?count?更改時更新
有更多資料視頻,加小可樂丫
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。