iT邦幫忙

2022 iThome 鐵人賽

DAY 21
2
Software Development

React框架白話文運動系列 第 21

React白話文運動21-React Hook-useLayoutEffect

  • 分享至 

  • xImage
  •  

前言

嗨,我是Hogan
目前在經營自己的自媒體 hogan.tech
主要分享一些有關於程式碼、軟體和科技業經驗分享
有興趣的讀者可以進一步關注我,進而獲得更多資訊唷!

未來文章一併更新於此網站 Hogan.B Lab
並且包含多語系 繁體中文英文日文簡體中文
觀看分類:React 白話文運動其他系列

如果想要快速查找其他文章請點選目錄

成立 hogan.tech 的初衷是
希望每個正在這條路上探索的人,
都可以透過 Hogan.tech 嘗試進入程式領域。


前一篇介紹與useMemo 相似的Hook - useCallback

  1. 複習 useMemo
  2. useCallback 寫法
  3. useCallback 與 useMemo 差異

這一篇會介紹另一個與useEffect 很像的Hook - useLayoutEffect

  1. 複習 useEffect
  2. 使用 useLayoutEffect
  3. Hook 的使用原則

複習 useEffect

這邊可以先來看一下這個元件,如果直接執行這個元件,

function Component() {
    console.log("render components");
    useEffect(() => {
        console.log("useEffect trigger")
    })
    return (
        <>
            {console.log("render JSX")}
        </>
    )
}

如果呼叫上面的元件,會依照順序印出三行的結果

分別為

  1. render components
  2. render JSX
  3. useEffect trigger

理由是因為載入元件後會先執行裡面的函式

Component元件第二行會先被執行,印出 render components

接下來遇到 useEffect 則會擺到渲染後去執行

然後再回傳 JSX 時,執行裡面的程式,印出 render JSX

最後回傳完畢後再去執行useEffect裡面的函式,印出 useEffect trigger

這樣的執行順序也與元件的生命週期有關

以上面的流程來說

  1. 執行元件
  2. 渲染元件
  3. 執行useEffect 函式

那如果我們修改元件的狀態,元件因此重新渲染呢?

除了渲染元件的步驟以外,其實也會重新執行 useEffect 裡面的函式

import React from 'react'
import { useState } from 'react';
import { useEffect } from 'react';

export default function EffectComponent() {
    console.log("render components");
    const [value, setValue] = useState(0)
    useEffect(() => {
        console.log("useEffect trigger")
        console.log(value)
    }, [value])
    return (
        <>
            <button onClick={() => setValue(value + 1)}></button>
            {console.log("render JSX")}
        </>
    )
}

這邊另外搭配了 useState 給予元件一個狀態叫做value,點擊按鈕後可以讓 value 加一

如果實際執行程式,並且點擊按鈕後,會分別印出以下

  1. render components
  2. render JSX
  3. useEffect trigger
  4. 0
  5. render components
  6. render JSX
  7. useEffect trigger
  8. 1

不過這邊也要跟讀者強調一下,如果不熟悉 useEffect 的讀者,可能一不小心就會陷入無窮渲染

import React from 'react'
import { useState } from 'react';
import { useEffect } from 'react';

export default function EffectComponent() {
    console.log("render components");
    const [value, setValue] = useState(0)
    useEffect(() => {
        console.log(value)
        setData()
    })

    const setData = () => {
        setValue(value + 1)
    }
    return (
        <>
            <button onClick={setData}></button>
            {console.log("render JSX")}
        </>
    )
}

如果直接執行以上的程式,會看到 console 一直跑,並且數字一直往上,為什麼呢?

理由是因為,以上面的流程來說

  1. 執行元件
  2. 渲染元件
  3. 執行useEffect 內的 setData 函式
  4. setData 函式修改狀態
  5. 狀態被修改,執行useEffect 內的 setData 函式
  6. setData 函式修改狀態
  7. ....

就會進入無窮渲染


使用 useLayoutEffect

首先要講解一下 useLayoutEffect 的觸發時機

  1. React 元件渲染
  2. useLayoutEffect 被執行
  3. React 更新瀏覽器的DOM
  4. useEffect 被執行

這邊也快速給予一個 useEffect 以及 useLayoutEffect 的例子:

import React, { useEffect, useLayoutEffect } from 'react';

function App(){
    useEffect(()=>console.log("useEffect render"));
    useLayoutEffect(()=>console.log("useLayoutEffect render"));
    return <></>
}

即便 useEffect 寫在 useLayoutEffect 前面,但是實際執行起來

useLayoutEffect 會是先印出訊息,才接著是 useEffect 印出訊息

那麼實際在哪些情況會做使用呢?

架設現在有一種情況是想要透過當前視窗大小,來去繪圖

那麼應該有兩件事情需要執行:

  1. 獲得當前視窗大小
  2. 來去繪圖

那問題來了,如果直接在useEffect 去執行這兩件事

顯然如果重新渲染,會一直重複去做獲得當前視窗大小

但如果直接將它放在useLayoutEffect 就不用擔心這個問題

import React, { useLayoutEffect, useState } from 'react';

function useMousePosition() {
    const [x, setX] = useState(0);
    const [y, setY] = useState(0);

    const setPosition = ({ x, y }) => {
        setX(x);
        setY(y);
    }

    useLayoutEffect(() => {
        window.addEventListener("mousemove", setPosition);
        return () => window.removeEventListener("mousemove", setPosition);
    }, [])

    return <></>
}

Hook 的使用原則

接下來就是一些文字的提醒了,在使用React Hook 的時候,常常遇到莫名的錯誤

所以有一些原則如果注意了,就可以大幅減少錯誤或是異常

Hook 只應該在React 元件中運作

目前我有寫到的範例程式應該不難發現,我都是在React元件中去做使用

一個很大的理由是 Hook 本身就是 React 的程式,而非JavaScript 內建的語法

所以如果直接擺到一般純 JavaScript 的語法中,一定會出錯

應該依據功能拆分成不同的 Hook

當系統越來越複雜,並且大量使用各種Hook 的時候,將功能拆成不同的 Hook 是相當重要的

以我個人工作經驗,我們團隊會將資料使用Store 自定義的Hook來去資料做控管,並且使用 Custom Hook 來去處理邏輯

避免在蜂巢狀的結構中呼叫Hook

通常我們在使用Hook時,不應該放進判別式或是迴圈之中

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

if(count > 5){
	useEffect(()=>{
		console.log(count);
		setCount(count+1);
	},[count])
}

以這上面的例子來說,如果在判別式之中放入 useEffect,代表這個 Hook 只有在count > 5時才會被執行

這會讓React 在比對Hook時的陣列時,因為長度不同而產生錯誤的行為


結語

這篇講解了另一個與useEffect 很相似的Hook,除了講解 useLayoutEffect 的使用方法以外

也另外提到一些使用Hook應該注意的原則

如果有任何建議與疑問也歡迎留言!

如果喜歡此系列文章,請不吝於按下喜歡及分享,讓更多人看到唷~


上一篇
React白話文運動20-React Hook-useCallback
下一篇
React白話文運動22-React Hook-useReducer
系列文
React框架白話文運動30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言