React Hooks瞭解一下?超簡單入門Hooks(上)
先來看一個可以管理狀態的元件在React中怎樣寫
import React from 'react' class Counter extends React.Component { state = { count: 0 } componentDidMount() { this.interval = setInterval(() => { this.setState({ count: this.state.count + 1 }) }, 1000); } componentWillUnmount() { if(this.interval) { clearInterval(this.interval) } } render() { return ( <div>count is : {this.state.count}</div> ) } } export default Counter; 複製程式碼
以上程式碼寫了一個簡單的計數器元件,管理了一個count狀態(就是一個名字叫count的資料),在元件載入完成後,執行定時器,將count狀態每秒+1,在元件要被解除安裝之前清除定時器(不清除會一直佔用記憶體,可能導致記憶體洩漏)。這裡新增定時器和解除安裝定時器都是通過生命週期鉤子來實現的,在Hooks引入之前,函式元件無法使用生命週期函式的,所以無法完成上述的狀態管理功能。
引入Hooks,讓函式元件也可以完成狀態管理
先簡單使用兩個Hooks,下文還會詳細介紹
import React, {useState, useEffect} from 'react' function CounterFunc() { const [count, setCount] = useState(0) useEffect(() => { const interval = setInterval(() => { setCount(c => c + 1) },1000) return () => clearInterval(interval) },[]) return ( <div>count is : {count}</div> ) } export default CounterFunc; 複製程式碼
這裡直接上了一段程式碼,在最開頭引入了兩個函式: useState
和 useEffect
。
在函式元件CounterFunc中,通過 useState(0)
來設定count的值,這裡 useState(0)
會返回一個數組,陣列第一個元素就是被賦值為0的變數,第二個元素是可以對第一個元素重新設定值得函式。我們這裡通過陣列解構的方式,將這兩個元素分別賦值給 count
和 setCount
,(如果不瞭解ES6的解構賦值,請先查閱阮一峰老師ES6書籍的相關部分)。
我們還用了 useEffect()
函式,在函式裡定義了一個定時器,然後返回了一個函式,在返回的函式中清除了定時器,這個被返回的函式就相當於類元件中的 componentWillUnmount()
生命周子函數了。而這個 useEffect()
函式本身又相當於 componentDidMount()
生命週期函式。
至此我們也能很輕鬆理解 useState
其實就相當於類元件中設定state的部分,它返回的陣列第二個元素 setCount()
是一個高階函式,我們需要傳入一個 c => c + 1
作為引數,這個高階函式會將 count
傳進 c => c + 1
並將計算後的值重新賦值給count。

State Hooks
useState()
對於狀態管理的Hooks,上面已經介紹了 useState()
,這裡會再加一個 useReducer()
, useReducer
才是基礎的狀態管理Hook
useState(0)
對useState傳遞的0只是對狀態設定了一個預設值,狀態改變後他就沒用了。
它返回的兩個元素我想大家應該也都清楚是什麼東西了,但是這裡要說的是第二個元素,它有兩種用法
setCount( 100 ) setCount( c => c + 1)
這裡說明一下我們上面為什麼不用 setCount( count + 1 )
的方式,有興趣的小夥伴可以試試,這樣我們頁面的結果始終都是1,而不是預期的1,2,3...... 為什麼會發生這種事?這是由於閉包造成的,在state更新後,會重新執行我們建立的函式元件,這時候 const [count, setCount] = useState(0)
這段程式碼會重新執行一遍,count每次都會被重置為0,然後更新為1。
閉包插曲:
上面說到了閉包,本來不想單獨說閉包,但是想了想,Hooks應該算是走函數語言程式設計的道路吧,在學Hooks時候,我們會遇到很多奇怪的現象,導致小夥伴感覺很難,其實很大程度上跟沒掌握好閉包有很大關係。這裡就簡單說一下。 Tips: 請注意,如果你看別的文章從未搞懂過閉包,別擔心,這裡我還是有信心讓90%看我文章的人搞清楚什麼是閉包。有基礎的直接跳過!!!
要說閉包先說作用域
我們這裡撇開let,const定義變數的方式,迴歸初心var 在JavaScript中,ES6出現之前是沒有塊級作用域的,但是它有個東西叫做函式作用域
var glob = '我是最外層定義的變數' function func1() { console.log(glob) // 這裡可以引用上層的變數 var f1 = '我是func1中的變數' function func1_1() { var f1_1 = '我是函式func1_1中的變數' console.log(glob) console.log(f1)// 這裡可以拿到上一層函式中定義的變數和最外層的變數 } function func1_1() { console.log(glob) // 這裡可以拿到最外層的變數 console.log(f1_1) // 但是拿不到同一級別函式定義的變數 } } 複製程式碼
上面這程式碼和註釋相信能讓大家看懂一點JavaScript中的作用域。一個函式,是不能拿到同級別函式裡面定義的變數,但是能拿到父級,爺爺級,爺爺爺級函式定義的變數。我們可以利用這裡特性對變數進行封裝。
function func1(param) { var f1 = '只讓我的子孫拿到的變數‘ var f2 = param; // 這裡把param賦值給f2,它也只能被自己子孫拿到 function func1_1() { console.log(f1, f2) //都能拿到 } return func1_1 } console.log(f1, f2) //兩個都拿不到,報錯 var res1 = func1('hello') // 你猜他的結果是什麼。 // 它的結果是一個函式,就是我們返回的func1_1這個函式,猜錯的自己罰自己,給我點個贊,哈哈哈。 res1() // res1存放的就是返回的func1_1,所以再把它執行一遍 //打印出 `只讓我的子孫拿到的變數hello` 複製程式碼
其實在我們執行func1('hello')之後,func1的生命週期就結束了,本來,它裡面定義的f1,f2都應該在它生命週期結束後就再也訪問不到了,但是幸運的是,func1_1替他儲存下了這兩個變數,使得 func1還沒有死透
這樣封裝變數就是閉包。
閉包插曲結束
繼續上面的來說
我們可以通過 setCount(c => c + 1)
傳入回撥函式的方式避免閉包帶來的 奇怪
現象。
useReducer
function countReducer(state, action) { switch(action.type) { case 'add': return state + 1; case 'minus': return state - 1; default: return state } } ...... // const [count, setCount] = useState(0) const [count, dispatchCount] = useReducer(countReducer,0); ...... // setCount(c => c + 1) dispatchCount({ type: 'add' }) ...... 複製程式碼
首先我們先在CounterFunc外面定義一個reducer,然後更改count的定義,以及count的更新,這裡useReducer的使用跟 redux中操作很像,後面會持續更新Redux的使用,請有需要的小夥伴關注一下,順手給個贊。
要注意的是, useReducer(countReducer,0)
的兩個引數,第一個是定義的countReducer第二個是預設值。我們接收返回值的第二個元素固定寫為dispach,或者這裡寫的dispachCount的格式。
小結
今天這個標題不寫結束,因為確實沒有結束,而我現在也從22:30開始寫到了00:29,真的寫的很慢,都是認真寫的,請大家堅持看完。所以寫的有點累了,而且篇幅也不是很短了,索性就分為上下篇吧,或許還是上中下篇。 這裡主要分享了5個知識點,一個是函式元件與類元件的區別,一個是useState, 再一個useEffect(後面會詳細說),然後介紹了閉包,最後說明了useReducer的使用,相信認真看到這裡的小夥伴對於React的Hooks已經有了個初步瞭解,並且對閉包不熟悉的小夥伴也已經對閉包有了深入認識吧。