閱讀此文前假設你對 React 的新特性 Hooks 有必定的瞭解,並能進行最基本的使用。若是還不清楚 React Hooks 是什麼,也不要緊,建議讀完本文後再去閱讀下官方關於 React Hooks 的文檔
今天,咱們要來聊一聊 React Hooks 官方當前提供的 useXXX
到底夠不夠用,是否能知足咱們平常的開發需求。git
: 喂喂,useState 這麼核心的 API,不就是專門用於處理 State 的嗎?
是的,一點都沒錯。用過 useState
的同窗應該都知道,原來的 this.setState
,如今能夠用 useState
的返回值中的方法 setXXX
代替,以下所示:github
const [ count, setCount ] = useState(0);
咱們來簡單對比一下 class
和 Hooks
寫法的差異:數組
# get state class: this.state.count Hooks: count # set state class: this.setState({count: 1}) Hooks: setCount(1)
: 這個太簡單了,瞭解過 React Hooks 的人都懂啊。因此你就是要給我講這個?
固然,不是啦。我問你,有沒有發現少了些什麼呢?原來的 this.setState()
,是否是有第二個參數?這第二個參數是否是一個callback方法?這個方法是否是會在狀態成功更新後調用呢?函數
: 你這麼說好像是哦,但callback我沒怎麼用,你能說下使用的場景嗎?
固然,信手拈來。fetch
聯動選擇下拉框的場景,好比城市選擇器。當選擇了省份後,就須要去獲取對應的城市列表,這個 moment 就會用到了。話很少說,先來看下 setState
的例子:this
class App extends React.Component { constructor() { this.state = { pId: 0 }; } onProvinceChanged(val) { this.setState({ pId: val }, () => { fetchCities(this.state.pId); }); } ... }
想一想若是沒有callback,咱們就比較難準確地在狀態更新後進行一些其它的操做了spa
: 因此 React Hooks 對此一籌莫展?我不信,我不信,我不信。
固然,不是啦。能夠用 useEffect
來實現這個需求。咱們來看下使用 Hooks
方式如何實現上面的功能3d
function App() { const [pId, setPId] = useState(0); useEffect( function() { fetchCities(pId); }, [pId] ); function onProvinceChanged(val) { setPId(val); } }
: 那就是能實現咯,因此你接下來到底想說什麼呢?
你仔細看下上面的代碼,是否是很像事件的模式,一個在監聽事件(useEffect 在監聽 pId 的變化,而後執行方法)一個在觸發事件(setPId)。code
事件模式能夠起到解耦代碼的做用,但同時意味着代碼很鬆散,一方只負責觸發事件,而不用去關心接下來會發生什麼。但咱們這裏的需求很明確,我選擇了省份,接下來確定是要加載城市數據,這兩步的邏輯是有關聯的,有前後順序的,固然但願捱得越緊越好,這樣代碼比較有條理性,閱讀起來比較順暢,容易理解。不信的話,你就認真對比下上面的兩個例子仔細推敲一下。component
反正我是以爲仍是得用回 callback 的方式,功能實現了當然重要,但代碼的可讀性,可維護性也很重要。
: 但你不是說 useState 沒提供 callback 嗎?那如今能怎麼作?
固然,官方目前確實沒有提供,但咱們能夠經過自定義 Hooks 的方式來實現。接下來咱們會使用到第三方開源庫 nice-hooks 來知足咱們的需求
把上面的例子用 nice-hooks 提供的方法從新寫一遍,直接上代碼,很是簡單:
import { useStateCB } from 'nice-hooks'; function App() { const [pId, setPId] = useStateCB(0); function onProvinceChanged(val) { setPId(val, newPID => { fetchCities(newPID); }); } }
你看,callback 的迴歸,讓 Hooks 在處理 state 方面至少保持與 this.setState
同等水平,不至於被吐槽落後了。
好了,Hooks 關於 state 的處理的介紹就告一段落了。
-------- ☕ 建議休息片刻,眺望遠方 --------
咱們都知道,每一個組件都有它從生到死的生命週期。在 React 中,用的比較多的是:componentDidMount
,componentDidUpdate
,componentWillUnmount
。
: React Hooks 也應該有與之對應的 useXXX 方法吧?
我也以爲是,但現實是咱們並無發現官方有提供與之對應的 useXXX 方法。我知道你想說什麼,放心,依然能夠用官方當前提供的 API 來實現這些生命週期。接下來咱們就一個一個來說解。
該生命週期方法會在組件掛載到DOM樹後執行,咱們能夠利用 useEffect
來達到這個目的,怎麼弄呢?看下例子吧
useEffect(() => { console.log('Do something, such as fetching data'); }, [])
傳個空數組,表明依賴項不變,因此只會執行一次,因此在組件第一次渲染完畢後執行一次,就至關於 componentDidMount 了
在組件即將銷燬時會執行該周期函數,那麼對應的,咱們依然能夠利用 useEffect 來達到這個目的,看下例子:
useEffect(() => { console.log('Do something, such as fetching data'); return function() { console.log('Do something before destroyed') } }, [])
由於銷燬動做在整個生命週期中也只會執行一次,因此咱們能夠在第一個例子的基礎上增長一個返回函數,該函數會在組件銷燬的時候執行
每當組件的 props,state 變化時,就會執行該周期函數,依然可使用 useEffect 來達到該目的
useEffect(() => { console.log('Do something when props / state changes') })
這裏並無提供依賴值,因此在每次渲染後都會執行,相似於 componentDidUpdate。但這裏有點小問題,就是在初始化時,這裏也會執行,即這裏會包括初始化,須要額外寫些代碼來支持,這裏就不展開了。
咱們還可使用 useEffect
來達到 watch 某個 state 或 props 變化的目的。因此你會發現,生命週期被混在了一堆 useEffect
代碼中,不是那麼的直接了當。
雖然 useEffect
能夠實現各類生命週期方法,但依然仍是那個問題,代碼的可讀性和可維護性很重要。咱們仍然能夠用 nice-hooks ,使用它的 useLifeCycle
方法來讓生命週期方法迴歸,用法很是簡單,代碼一目瞭然,就不囉嗦了,省得有湊字數嫌疑
useLifeCycle({ didMount() { console.log('Do something, such as fetching data'); }, didUpdate() { console.log('Do something when props / state changes') }, willUnmount() { console.log('Do something before destroyed') } });
另外,class
組件寫法的生命週期方法有個小缺陷,就是當須要在註銷時銷燬初始化時聲明的一些東西,如事件監聽,如定時器,註冊和銷燬的邏輯被強行拆開,容易疏忽而致使Bug,因此 useLifeCycle
提供了一個 didMountAndWillUnmount
配置來寫成對的邏輯,以下
useLifeCycle({ didMountAndUnmount: [ { didMount() { console.log('register foo event) }, willUnmount() { console.log('unregister foo event) } }, { didMount() { console.log('register bar event) }, willUnmount() { console.log('unregister bar event) } } ] })
那麼推薦的作法就是須要寫成對邏輯的寫在 didMountAndWillUnmount,而不須要做銷燬處理的初始化操做就寫在 didMount,willUnmount這裏就寫一些沒有成對邏輯的銷燬操做
-------- ☕ 建議休息片刻,聽聽音樂 --------
在使用 Hooks 寫組件時,由於如今是純函數組件,因此無法像 class
同樣聲明實例變量,如下使用變量是會有問題的
function comp() { let name = 'daniel'; }
: 看着挺正常的啊,函數內聲明變量再正常不過的了。有啥問題呢?
你可能會在某個地方修改了 name 的值,並但願使用 name 變量時它的值是最後修改的值。惋惜事與願違,由於每次組件從新渲染時,渲染函數都會從新執行,那該變量就會被從新初始化。
: 好像是哦,如今就只剩下一個 render 函數,每次都會從新執行,那昨辦呢?
咱們能夠利用 useRef
這個官方 hook,它的 current
屬性會一直保持最後的值,以下:
function comp() { const nameRef = useRef('daniel'); function someFn() { // get let name = nameRef.current; // set nameRef.current = 'sarah'; } }
一旦咱們修改了 current
屬性的值,那麼下次從新渲染時,該 current
值會保持最後修改的值,即達到了實例變量的效果。
: 按照上面的套路,你確定會說代碼可讀性不太好之類的
哎呀,臺詞都被你搶了。但我仍然仍是要說,雖然這樣利用 useRef
是達到了目的,但代碼看起來老是不那麼友好。
這裏依然建議使用 nice-hooks,它的 useInstanceVar
hook,用法與 useState 同樣,差異就是 setXXX 的時候只是改變實例變量的值而已,並不會致使從新渲染,示例以下:
function comp() { const [nameVar, setNameVar] = useInstanceVar('daniel'); function someFn() { // get nameVar; // set setNameVar('sarah'); } }
建議在聲明變量名時,使用 Var
做爲後綴以區分開 state,如 [ xxxVar, setXXXVar ]
====== 華麗麗的結束分割線 ======
以上都是使用 nice-hooks 這個第三方開源庫來使 React Hooks 更好用。
若是你有好的建議,請多給這個開源項目提提issues;
若是以爲對你有用,那還請給這個開源項目加個星唄。
感謝閱讀!