本文是 React 系列的第二篇前端
想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!react
Hook 是 React 16.8 的新增特性。它可讓你在不編寫 類組件 的狀況下使用 state
以及其餘的 React
特性。git
狀態邏輯複用難github
趨向複雜難以維護npm
this 指向困擾數組
this
優化類組件的三大問題dom
函數組件無 this 問題函數
自定義 Hook 方便複用狀態邏輯post
反作用的關注點分離學習
import React, {Component} from 'react'
class App extends Component {
state = {
count: 0
};
render() {
const {count} = this.state;
return (
<button type="button"
onClick={() => {
this.setState({
count: count + 1
})
}}
>Click({count})</button>
)
}
}
export default App;
複製代碼
以上代碼很好理解,點擊按鈕讓 count
值加 1
。
接下來咱們使用 useState
來實現上述功能。
import React, {useState} from 'react'
function App () {
const [count, setCount] = useState(0)
return (
<button type="button"
onClick={() => {setCount(count + 1) }}
>Click({count})</button>
)
}
複製代碼
在這裏,useState
就是一個 Hook。經過在函數組件裏調用它來給組件添加一些內部 state
,React 會在重複渲染時保留這個 state
。
useState
會返回一對值**:當前狀態**和一個讓你更新它的函數。你能夠在事件處理函數中或其餘一些地方調用這個函數。它相似 class
組件的 this.setState
,可是它不會把新的 state
和舊的 state
進行合併。useState
惟一的參數就是初始 state
。
useState
讓代碼看起來簡潔了,可是咱們可能會對組件中,直接調用 useState
返回的狀態會有些懵。既然 userState
沒有傳入任何的環境參數,它怎麼知道要返回的的是 count
的呢,並且仍是這個組件的 count
不是其它組件的 count
。
初淺的理解: useState
確實不知道咱們要返回的 count
,但其實也不須要知道,它只要返回一個變量就好了。數組解構的語法讓咱們在調用 useState
時能夠給 state
變量取不一樣的名字。
useState
怎麼知道要返回當前組件的 state
?
由於 JavaScript 是單線程的。在 useState
被調用時,它只能在惟一一個組件的上下文中。
有人可能會問,若是組件內有多個 usreState
,那 useState
怎麼知道哪一次調用返回哪個 state
呢?
這個就是按照第一次運行的次序來順序來返回的。
接着上面的例子咱們在聲明一個 useState
:
...
const [count, setScount] = useState(0)
const [name, setName] = useState('小智')
...
複製代碼
而後咱們就能夠判定,之後APP
組件每次渲染的時候,useState
第一次調用必定是返回 count
,第二次調用必定是返回 name
。不信的話來作個實驗:
let id = 0
function App () {
let name,setName;
let count,setCount;
id += 1;
if (id & 1) {
// 奇數
[count, setCount] = useState(0)
[name, setName] = useState('小智')
} else {
// 偶數
[name, setName] = useState('小智')
[count, setCount] = useState(0)
}
return (
<button type="button"
onClick={() => {setCount(count + 1) }}
>
Click({count}), name ({name})
</button>
)
}
複製代碼
首先在外部聲明一個 id,當 id爲奇數和偶數的時候分別讓 useState 調用方式相反,運行會看到有趣的現象。
當前版本若是寫的順序不一致就會報錯。
會發現 count
和 name
的取值串了。咱們但願給 count 加 1
,如今卻給 name 加了 1
,說明 setCount
函數也串成了 setName
函數。
爲了防止咱們使用 useState 不當,React 提供了一個 ESlint 插件幫助咱們檢查。
經過上述咱們知道 useState
有個默認值,由於是默認值,因此在不一樣的渲染週期去傳入不一樣的值是沒有意義的,只有第一次傳入的纔有效。以下所示:
...
const defaultCount = props.defaultCount || 0
const [count, setCount] = useState(defaultCount)
...
複製代碼
state
的默認值是基於 props
,在 APP 組件每次渲染的時候 const defaultCount = props.defaultCount || 0
都會運行一次,若是它複雜度比較高的話,那麼浪費的資料確定是可觀的。
useState
支持傳入函數,來延遲初始化:
const [count, setCount] = useState(() => {
return props.defaultCount || 0
})
複製代碼
Effect Hook 可讓你在函數組件中執行反作用操做。數據獲取,設置訂閱以及手動更改 React 組件中的 DOM 都屬於反作用。無論你知不知道這些操做,或是"反作用"這個名字,應該都在組件中使用過它們。
Mount 以後 對應 componentDidMount
Update 以後 對應 componentDidUpdate
Unmount 以前 對應 componentWillUnmount
如今使用 useEffect
就能夠覆蓋上述的狀況。
爲何一個 useEffect
就能涵蓋 Mount,Update,Unmount
等場景呢。
useEffect 標準上是在組件每次渲染以後調用,而且會根據自定義狀態來決定是否調用仍是不調用。
第一次調用就至關於componentDidMount
,後面的調用至關於 componentDidUpdate
。useEffect
還能夠返回另外一個回調函數,這個函數的執行時機很重要。做用是清除上一次反作用遺留下來的狀態。
好比一個組件在第三次,第五次,第七次渲染後執行了 useEffect
邏輯,那麼回調函數就會在第四次,第六次和第八次渲染以前執行。嚴格來說,是在前一次的渲染視圖清除以前。若是 useEffect
是在第一次調用的,那麼它返回的回調函數就只會在組件卸載以前調用了,也就是 componentWillUnmount
。
若是你熟悉 React class 的生命週期函數,你能夠把 useEffect Hook 看作
componentDidMount
,componentDidUpdate
和componentWillUnmount
這三個函數的組合。
舉粟說明一下:
class App extends Component {
state = {
count: 0,
size: {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
}
};
onResize = () => {
this.setState({
size: {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
}
})
}
componentDidMount () {
document.title = this.state.count;
window.addEventListener('resize', this.onResize, false)
}
componentWillMount () {
window.removeEventListener('resize', this.onResize, false)
}
componentDidUpdate () {
document.title = this.state.count;
}
render() {
const {count, size} = this.state;
return (
<button type="button"
onClick={() => {this.setState({count: count + 1})}}
>
Click({count})
size: {size.width}x{size.height}
</button>
)
}
}
複製代碼
上面主要作的就是網頁 title
顯示count
值,並監聽網頁大小的變化。這裏用到了componentDidMount
,componentDidUpdate
等反作用,由於第一次掛載咱們須要把初始值給 title, 當 count
變化時,把變化後的值給它 title
,這樣 title
才能實時的更新。
注意,咱們須要在兩個生命週期函數中編寫重複的代碼。
這邊咱們容易出錯的地方就是在組件結束以後要記住銷燬事件的註冊,否則會致使資源的泄漏。如今咱們把 App 組件的反作用用 useEffect
實現。
function App (props) {
const [count, setCount] = useState(0);
const [size, setSize] = useState({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
});
const onResize = () => {
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
}
)
}
useEffect(() => {
document.title = count;
})
useEffect(() => {
window.addEventListener('resize', onResize, false);
return () => {
window.removeEventListener('resize', onResize, false)
}
}, [])
return (
<button type="button"
onClick={() => {setCount(count + 1) }}
>
Click({count})
size: {size.width}x{size.height}
</button>
)
}
複製代碼
對於上述代碼的第一個 useEffect
,相比類組件,Hooks 不在關心是 mount 仍是 update。用useEffect
統一在渲染後調用,就完整追蹤了 count
的值。
對於第二個 useEffect
,咱們能夠經過返回一個回調函數來註銷事件的註冊。回調函數在視圖被銷燬以前觸發,銷燬的緣由有兩種:從新渲染和組件卸載。
這邊有個問題,既然 useEffect
每次渲染後都執行,那咱們每次都要綁定和解綁事件嗎?固然是徹底不須要,只要使用 useEffect
第二個參數,並傳入一個空數組便可。第二個參數是一個可選的數組參數,只有數組的每一項都不變的狀況下,useEffect
纔不會執行。第一次渲染以後,useEffect 確定會執行。因爲咱們傳入的空數組,空數組與空數組是相同的,所以 useEffect
只會在第一次執行一次。
這也說明咱們把 resize
相關的邏輯放在一直寫,不在像類組件那樣分散在兩個不一樣的生命週期內。同時咱們處理 title 的邏輯與 resize 的邏輯分別在兩個 useEffect 內處理,實現關注點分離。
咱們在定義一個 useEffect,來看看經過不一樣參數,第二個參數的不一樣做用。
...
useEffect(() => {
console.log('count:', count)
}, [count])
...
複製代碼
第二個參數咱們傳入 [count]
, 表示只有 count 的變化時,我纔打印 count
值,resize
變化不會打印。
運行效果以下:
第二個參數的三種形態,undefined
,空數組及非空數組,咱們都經歷過了,可是我們沒有看到過回調函數的執行。
如今有一種場景就是在組件中訪問 Dom 元素,在 Dom元素上綁定事件,在上述的代碼中添加如下代碼:
...
const onClick = () => {
console.log('click');
}
useEffect(() => {
document.querySelector('#size').addEventListener('click', onClick, false);
},[])
return (
<div>
...
<span id="size">size: {size.width}x{size.height}</span>
</div>
)
複製代碼
新增一個 DOM 元素,在新的 useEffect
中監聽 span
元素的點擊事件。
運行效果:
假如咱們 span 元素能夠被銷燬重建,咱們看看會發生什麼狀況,改造一下代碼:
return (
<div>
...
</button>
{
count%2
? <span id="size">我是span</span>
: <p id='size'>我是p</p>
}
</div>
複製代碼
運行效果:
能夠看出一旦 dom 元素被替換,咱們綁定的事件就失效了,因此我們始終要追蹤這個dom 元素的最新狀態。
使用 useEffect
,最合適的方式就是使用回調函數來處理了,同時要保證每次渲染後都要從新運行,因此不能給第二次參數設置 []
,改造以下:
useEffect(() => {
document.querySelector('#size').addEventListener('click', onClick, false);
return () => {
document.querySelector('#size').removeEventListener('click', onClick, false);
}
})
複製代碼
運行結果:
乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。
我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!
關注公衆號,後臺回覆福利,便可看到福利,你懂的。