iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 25
1
Modern Web

給初入JS框架新手的React.js入門系列 第 25

【React.js入門 - 25】 監控瀏覽器長寬 - 以React hook實現

  • 分享至 

  • xImage
  •  

(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問


在React.js專案中,「監控瀏覽器長寬」是很常寫到的功能,我們來試著用React hook實現,並以Custom hook把這個功能模組化。

這一篇主要是給對DOM操作沒有那麼熟悉、以實現功能為導向的人。如果你很熟悉操作DOM了,這篇就當作是上一篇的練習吧。

step 0 : 認識要用到的函式/api

要「監控」,就一定會有:

  1. addEventListener(事件,函式)
  2. removeEventListener(事件,函式)

而視窗(window)「改變長寬」所觸發的event是'resize'。我們只要在Custom hook中監控視窗的這個事件,並且透過window.innerWidth去取得目前視窗寬度,元件就能搭配這個hook判斷自己該呈現RWD中的哪個樣貌。

step 1 : 建立custom hook的基本架構

新建立一個useRWD.js,在裡面宣告custom hook的函式架構,命名為useRWD,並export。

const useRWD=()=>{

}

export default useRWD;

step 2 : 引入需要的React hook

先前在講生命週期的時候講過,如果我們需要監聽事件,必須「在componentDidMount加入、在componentWillUnmount移除」。同時我們需要一個state去記錄並回傳目前螢幕的狀況,所以總共需要的hook為:

import { useState, useEffect } from 'react';

step 3 : 宣告並回傳用來記錄目前螢幕狀態的state

這邊我們要用到useState建立state,並且給他一個初始值「mobile」,然後在函式的最後面回傳。等等我們會講為什麼這裡是以mobile為初始值。

import { useState, useEffect } from 'react';
const useRWD=()=>{
    const [device,setDevice]=useState("mobile");
    
    return device;
}

export default useRWD;

step 4 : 定義用來綁定的函式

我們希望在螢幕長寬被改變時,根據目前的寬去判斷使用者的裝置是電腦、平板還是手機,並且記錄在state中。因此,我們要用來綁定的函式為:

    const handleRWD=()=>{
        if(window.innerWidth > 768)
            setDevice("PC");
        else if (window.innerWidth > 576)
            setDevice("tablet");
        else
            setDevice("mobile");
    }

這邊用是用和Boostrap相同的標準來分辨裝置。

step 5 : 定義生命週期,綁定事件。

useEffect建立並分出第一次渲染後(componentDidMount,以空陣列為第二參數)和元件移除時(componentWillUnmount,第一參數函式之return函式),並且分別在裡面加入和移除監聽事件。當事件產生時,觸發我們剛剛寫好的handleRWD

import { useState,useEffect} from 'react';

const useRWD=()=>{
    const [device,setDevice]=useState("mobile");

    const handleRWD=()=>{
        if(window.innerWidth>768)
            setDevice("PC");
        else if (window.innerWidth>576)
            setDevice("tablet");
        else
            setDevice("mobile");
    }

    useEffect(()=>{ 
        window.addEventListener('resize',handleRWD);
        return(()=>{
            window.removeEventListener('resize',handleRWD);
        })
    },[]);

    return device;
}

export default useRWD;

step 6 : 在App.js引入並使用useRWD

這邊為了要方便觀察我們custom hook的效果,我們讓App.js在不同裝置下會印出不同顏色的字。

import React from 'react';
import useRWD from './useRWD';

const App=()=>{
    const device=useRWD();

    if(device==="PC")
      return(  <h1 style={{color:"#354458",fontFamily:"Microsoft JhengHei"}}>電腦</h1>  );
    else if(device==="tablet")
      return(  <h1 style={{color:"#3a9ad9",fontFamily:"Microsoft JhengHei"}}>平板</h1>  );
    else
      return(  <h1 style={{color:"#29aba4",fontFamily:"Microsoft JhengHei"}}>手機</h1>  );
}
export default App;

完成之後我們試著執行看看:

你會發現,雖然大部份的時候useRWD能正常偵測,但是一開始的時候,不管螢幕寬度是多少,都會顯示為「手機」,一改變視窗長寬又會回歸正常。

嗯? 為什麼會這樣? 我們不是監控了螢幕長寬了嗎?

這是因為我們監聽的事件是「視窗長寬被改變」。在剛打開網頁時,我們的視窗並沒有被改變長寬,所以不會觸發我們定義的函式,state不論在任何裝置下,第一次都會回傳我們所給予的初始值。

這也是為什麼剛剛我們要用mobile當作初始值,因為這樣才能注意到這件事。

step 7: 最後一步 - 在第一次渲染後主動觸發一次handleRWD

我們只要在第一次渲染後(componentDidMount)呼叫一次handleRWD,在render後就能不需要等螢幕被改變,根據目前的螢幕寬度判斷應該是哪個裝置。

import { useState,useEffect} from 'react';

const useRWD=()=>{
    const [mobile,setMobile]=useState("mobile");

    const handleRWD=()=>{
        if(window.innerWidth>768)
            setMobile("PC");
        else if (window.innerWidth>576)
            setMobile("tablet");
        else
            setMobile("mobile");
    }

    useEffect(()=>{
    
        window.addEventListener('resize',handleRWD);
        handleRWD(); //加入此行
        
        return(()=>{
            window.removeEventListener('resize',handleRWD);
        })
    },[]);

    return mobile;
}

export default useRWD;

執行結果:

小結

在使用現代框架後,做RWD時就常常會需要一個像是css中media query的監測器,來讓元件在不同裝置下呈現不同樣貌。如果是之前學了javascript、但在DOM的操作沒有什麼經驗的人,可能不容易很快想出具體在React的實現方法。這一篇就是為了這些人而寫。

下一篇會來講我們一直沒有特別談的React中使用input的基本方法(控制組件)。


上一篇
【React.js入門 - 24】 Custom hook - 給我另一個超推React hook的理由
下一篇
【React.js入門 - 26】 input使用、input與state的互動 (控制組件) 、其他輸入元素
系列文
給初入JS框架新手的React.js入門31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言