React Hooks 的鉤子夠用嗎?

68747470733a2f2f7373312e62647374617469632e636f6d2f37306346765853685f5131596e78476b706f574b314846366868792f69742f753d323336393934373531392c393134343033373226666d3d31352667703d302e6a7067.jpg

閱讀此文前假設你對 React 的新特性 Hooks 有必定的瞭解,並能進行最基本的使用。若是還不清楚 React Hooks 是什麼,也不要緊,建議讀完本文後再去閱讀下官方關於 React Hooks 的文檔

今天,咱們要來聊一聊 React Hooks 官方當前提供的 useXXX 到底夠不夠用,是否能知足咱們平常的開發需求。git

先說說最重要的 State

: 喂喂,useState 這麼核心的 API,不就是專門用於處理 State 的嗎?

是的,一點都沒錯。用過 useState 的同窗應該都知道,原來的 this.setState,如今能夠用 useState 的返回值中的方法 setXXX 代替,以下所示:github

const [ count, setCount ] = useState(0);

咱們來簡單對比一下 classHooks 寫法的差異:數組

# 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 的處理的介紹就告一段落了。

-------- ☕ 建議休息片刻,眺望遠方 --------

接下來聊聊 Life Cycle 生命週期

咱們都知道,每一個組件都有它從生到死的生命週期。在 React 中,用的比較多的是:componentDidMountcomponentDidUpdatecomponentWillUnmount

: React Hooks 也應該有與之對應的 useXXX 方法吧?

我也以爲是,但現實是咱們並無發現官方有提供與之對應的 useXXX 方法。我知道你想說什麼,放心,依然能夠用官方當前提供的 API 來實現這些生命週期。接下來咱們就一個一個來說解。

  • componentDidMount

該生命週期方法會在組件掛載到DOM樹後執行,咱們能夠利用 useEffect 來達到這個目的,怎麼弄呢?看下例子吧

useEffect(() => {
  console.log('Do something, such as fetching data');    
}, [])

傳個空數組,表明依賴項不變,因此只會執行一次,因此在組件第一次渲染完畢後執行一次,就至關於 componentDidMount 了

  • componentWillUnmout

在組件即將銷燬時會執行該周期函數,那麼對應的,咱們依然能夠利用 useEffect 來達到這個目的,看下例子:

useEffect(() => {
  console.log('Do something, such as fetching data');
  return function() {
      console.log('Do something before destroyed')
  }
}, [])

由於銷燬動做在整個生命週期中也只會執行一次,因此咱們能夠在第一個例子的基礎上增長一個返回函數,該函數會在組件銷燬的時候執行

  • componentDidUpdate

每當組件的 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這裏就寫一些沒有成對邏輯的銷燬操做

-------- ☕ 建議休息片刻,聽聽音樂 --------

最後談談 Instance Variables 實例變量

在使用 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;

若是以爲對你有用,那還請給這個開源項目加個星唄。

感謝閱讀!

相關文章
相關標籤/搜索