Hook 是 React 16.8 的新增特性。它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性javascript
從官網的這句話中,咱們能夠明確的知道,Hook
增長了函數式組件中state
的使用,在以前函數式組件是沒法擁有本身的狀態,只能經過props
以及context
來渲染本身的UI
,而在業務邏輯中,有些場景必需要使用到state
,那麼咱們就只能將函數式組件定義爲class
組件。而如今經過Hook
,咱們能夠輕鬆的在函數式組件中維護咱們的狀態,不須要更改成class
組件。html
React Hooks
要解決的問題是狀態共享,這裏的狀態共享是指只共享狀態邏輯複用,並非指數據之間的共享。咱們知道在React Hooks
以前,解決狀態邏輯複用問題,咱們一般使用higher-order components
和render-props
,那麼既然已經有了這兩種解決方案,爲何React
開發者還要引入React Hook
?對於higher-order components
和render-props
,React Hook
的優點在哪?java
咱們先來看一下React
官方給出的React Hook
的demo
ajax
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
的話,如何實現npm
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
進行合併redux
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是一種禍害bash
由於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 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>
<h1>移動鼠標!</h1>
<Mouse render={mouse => (
)}/>
</div>
);
}
}
複製代碼
然而一般咱們說的Render Props
是由於模式才被稱爲 Render Props
,又不是由於必定要用render
對prop
進行命名。咱們也能夠這樣來表示
<Mouse>
{mouse => (
<Cat mouse={mouse} />
)}
</Mouse>
複製代碼
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
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 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
本質就是 JavaScript
函數,可是在使用它時須要遵循兩條規則。而且React
要求強制執行這兩條規則,否則就會出現異常的bug
Hook
不要在循環,條件或嵌套函數中調用 Hook, 確保老是在你的 React
函數的最頂層調用他們
React
函數中調用 Hook
不要在普通的 JavaScript 函數中調用 Hook
這兩條規則出現的緣由是,咱們能夠在單個組件中使用多個State Hook
或 Effect Hook
,React
靠的是 Hook
調用的順序來知道哪一個 state
對應哪一個useState
function Form() {
const [name1, setName1] = useState('Arzh1');
const [name2, setName2] = useState('Arzh2');
const [name3, setName3] = useState('Arzh3');
// ...
}
// ------------
// 首次渲染
// ------------
useState('Arzh1') // 1. 使用 'Arzh1' 初始化變量名爲 name1 的 state
useState('Arzh2') // 2. 使用 'Arzh2' 初始化變量名爲 name2 的 state
useEffect('Arzh3') // 3. 使用 'Arzh3' 初始化變量名爲 name3 的 state
// -------------
// 二次渲染
// -------------
useState('Arzh1') // 1. 讀取變量名爲 name1 的 state(參數被忽略)
useState('Arzh2') // 2. 讀取變量名爲 name2 的 state(參數被忽略)
useEffect('Arzh3') // 3. 讀取變量名爲 name3 的 state(參數被忽略)
複製代碼
若是咱們違反React
的規則,使用條件渲染
if (name !== '') {
const [name2, setName2] = useState('Arzh2');
}
複製代碼
假設第一次(name !== '')
爲true
的時候,執行此Hook
,第二次渲染(name !== '')
爲false
時,不執行此Hook
,那麼Hook
的調用順序就會發生變化,產生bug
useState('Arzh1') // 1. 讀取變量名爲 name1 的 state
//useState('Arzh2') // 2. Hook被忽略
useEffect('Arzh3') // 3. 讀取變量名爲 name2(以前爲name3) 的 state
複製代碼
React
不知道第二個 useState
的 Hook
應該返回什麼。React
會覺得在該組件中第二個 Hook
的調用像上次的渲染同樣,對應的是 arzh2
的 useState
,但並不是如此。因此這就是爲何React
強制要求Hook
使用必須遵循這兩個規則,同時咱們可使用 eslint-plugin-React-Hooks
來強制約束
咱們在上面的代碼中增長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
只會執行一次。
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 更改時更新
複製代碼
因爲篇幅緣由,就再也不此展開了,有興趣能夠自行官網查看