(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問
在React.js專案中,「監控瀏覽器長寬」是很常寫到的功能,我們來試著用React hook實現,並以Custom hook把這個功能模組化。
這一篇主要是給對DOM操作沒有那麼熟悉、以實現功能為導向的人。如果你很熟悉操作DOM了,這篇就當作是上一篇的練習吧。
要「監控」,就一定會有:
而視窗(window)「改變長寬」所觸發的event是'resize'
。我們只要在Custom hook中監控視窗的這個事件,並且透過window.innerWidth
去取得目前視窗寬度,元件就能搭配這個hook判斷自己該呈現RWD中的哪個樣貌。
新建立一個useRWD.js,在裡面宣告custom hook的函式架構,命名為useRWD
,並export。
const useRWD=()=>{
}
export default useRWD;
先前在講生命週期的時候講過,如果我們需要監聽事件,必須「在componentDidMount加入、在componentWillUnmount移除」。同時我們需要一個state去記錄並回傳目前螢幕的狀況,所以總共需要的hook為:
import { useState, useEffect } from 'react';
這邊我們要用到useState
建立state,並且給他一個初始值「mobile」,然後在函式的最後面回傳。等等我們會講為什麼這裡是以mobile為初始值。
import { useState, useEffect } from 'react';
const useRWD=()=>{
const [device,setDevice]=useState("mobile");
return device;
}
export default useRWD;
我們希望在螢幕長寬被改變時,根據目前的寬去判斷使用者的裝置是電腦、平板還是手機,並且記錄在state中。因此,我們要用來綁定的函式為:
const handleRWD=()=>{
if(window.innerWidth > 768)
setDevice("PC");
else if (window.innerWidth > 576)
setDevice("tablet");
else
setDevice("mobile");
}
這邊用是用和Boostrap相同的標準來分辨裝置。
用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;
這邊為了要方便觀察我們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當作初始值,因為這樣才能注意到這件事。
我們只要在第一次渲染後(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的基本方法(控制組件)。