iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 16
0
Modern Web

使用 React 製作簡易專案管理網站:從基礎到實戰系列 第 16

[Day 16] React 保衛戰 - 跳脫 React 的掌控「useEffect」

  • 分享至 

  • xImage
  •  

命令式編程 vs 宣告式編程

在開始進入正式主題前,我們先來談談兩種不同的編程方式 - 命令式編程(imperative programming)與宣告式編程(declaritive programming)。

首先從定義開始:

  • 命令式:告訴電腦如何做(HOW)來得到我們想要的結果(WHAT)
  • 宣告式:告訴電腦我們想要的結果(WHAT),讓電腦決定如何做(HOW)

嗯...有點難以理解,舉個搭計程車的例子。

我們一般都會和計程車司機說我們想要前往的目的地,至於計程車司機要開哪一條路、在哪一個路口轉彎、開得多快多慢等等我們都不需要煩惱,只要交由司機決定就好,但有些人就是比較熱心(?),一路上會提醒司機要走哪一條小路、在哪裡轉彎就不用等紅燈、哪裡有測速照相機要開慢一點等等,告訴司機到底怎麼開才會到達目的地。

前者就是宣告式編程,而後者就是命令式編程,雖然兩者的結果是一樣的,但是過程是全然不同的,當然也都各具有優缺點。

宣告式編程可以交由我們自己去決定我們認為最適合的方式,然後告訴電腦怎麼做來得我們想要的結果,但如果電腦已經知道一套很好的方式,我們何不節省時間與腦力,透過命令式編程直接告訴電腦我們要的結果,讓電腦來處理中間的過程呢?

這就是 React 的設計理念之一。

jQuery vs React

如果我們要偵測並且顯示一個按鈕的點擊次數,jQuery 的大致做法如下:

$("button").click(function() {
  const number = Number($("#number").text());
  $("p").text(number + 1);
})

而 React 的做法則是:

<button onClick={setNumber(prevNumber => prevNumber + 1)}</button>
<p>{number}<p>

我們可以看到使用 jQuery 的話我們必須明確的寫出如何更新畫面上的數字(命令式);若使用 React 我們只需要定義好資料和畫面的關係,如何更新畫面則交由 React 來處理(宣告式)。

在以上的例子中也許看不出來太大的差別,但當我們的畫面和操作邏輯越來越複雜,使用命令式編程出來的程式碼就會變得難以管理、擴充與維護,而使用宣告式編程則可以忽略操作邏輯的程式碼,只需專注於處理資料和畫面的關係。

跳脫 React 的掌控

React 並不是萬能的。

React 強大的地方在於 DOM 的管理與更新,但 DOM 之外的操作並不在 React 宣告式編程的掌握之中,例如發送 API 請求、資料訂閱、計時器、日誌紀錄等等我們稱之為會產生 side effect 的行為,因此 React 提供 useEffect 將這些行為延後,直到 virtual DOM 的變化更新到 real DOM 之後再一併處理。

useEffect 的使用方式

React.useEffect(effect, dependencies);
  • effect 為產生 side effect 的函式,可以返回一個清除 side effect 的函式
  • dependencies 為一 array,決定 effect 函式在元件 re-render 時是否要重新執行,如果 array 中的任一值改變則重新執行

例如:

fuction Timer() {
  React.useEffect(() => {
    const timer = setInterval(() => {
      console.log('tick')
    }, props.interval);
  
    return () => {
      clearInterval(timer);
    }
  }, [props.interval]);
  
  return null;
}
  • 第一次 render 之後:執行 effect 函式(開始每隔 props.interval 毫秒印一次 tick)
  • rerender 但 props.interval 不變:(繼續每隔 props.interval 毫秒印一次 tick)
  • rerender 且 props.interval 改變:先執行清除 effect 的函式(停止每隔 props.interval 毫秒印一次 tick),然後再執行 effect 函式(開始每隔 props.interval 毫秒印一次 tick)

只執行一次 effect 函式

如果 useEffect 的第二個參數 dependencies 傳入一個空 array,則只會執行一次 effect 函式:

React.useEffect(effect, []);

每次 rerender 都執行 effect 函式

如果 useEffect 不傳入第二個參數 dependencies 則每次元件 rerender 都會執行 effect 函式:

React.useEffect(effect);

使用 useEffect 開發是一種完全不同的心智模式

在 React 16.8 之前,React 開發者只能使用類別元件來處理 side effect,因為只有類別元件提供各種不同的生命週期函式,常用的有 componentDidMount、componentDidUpdate、componentWillUnmount 等等,因此在學習 useEffect 時大家習慣用類別元件的生命週期函式來和 useEffect 類比,但 useEffect 其實提供一種完全不同的開發的心智模式:

每次元件 render 並沒有什麼不同(不論是第一次 render 還是之後每一次的 re-render),我們都應該用同樣的方式去處理**

不然 React 提供 useComponentDidMount、useComponentDidUpdate、useComponentWillUnmount 這些 API 不就好了嗎?透過連結我們既有的知識的確可以幫助我們學習,但有可能使我們被既有的知識框架綁架,身處於一個技術快速發展的環境,值得我們再三思考,大家共勉之。

參考資料:

  1. Hooks API Reference – React - https://reactjs.org/docs/hooks-reference.html#useeffect
  2. 命令式编程(Imperative) vs声明式编程( Declarative) - 知乎 - https://zhuanlan.zhihu.com/p/34445114
  3. React Native教學 Part 3.5 - 概念分析:Declarative Programming - Carson's Tech Note - https://carsonwah.github.io/react-native-part3.5-declarative.html

上一篇
[Day 15] React 保衛戰 - 咻!元件與元件透過「context」隔空傳資料
下一篇
[Day 17] React 保衛戰 - 以不變應萬變「useRef」
系列文
使用 React 製作簡易專案管理網站:從基礎到實戰30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言