likes
comments
collection
share

🤔️怎么做?实现一个优雅的监听屏幕宽度的hook

作者站长头像
站长
· 阅读数 13

监听屏幕宽度,我们项目组之前通常解决方法是window.onresize方法。

const callback = (e)=>{
  console.log('screen width has changed',e.target.window.innerWidth);
}

window.addEventListener('resize',callback);

window.matchMedia

这里介绍一个新的监听屏幕宽度的方法,性能更高

const mediaQuery = window.matchMedia("(min-width: 720px)");

console.log("is large 720px: ", mediaQuery.matches);

直接使用matchMedia的API,就可以判断当前的屏幕宽度是否大于720px。传入的参数和css媒体查询一致,也就意味着matchMedia不仅仅可以查询屏幕宽度,屏幕是否倾斜,屏幕的像素,还有屏幕是否支持指针等等仅仅判断当前屏幕尺寸还是不够的,我们还需要知道屏幕尺寸什么变化为此,matchMedia提供了增加屏幕监听函数的功能

const listener = (mediaQuery)=>{
  console.log("is large 720px: ", mediaQuery.matches);
}
mediaQuery.addListener(listener);

效果:🤔️怎么做?实现一个优雅的监听屏幕宽度的hook可以看到只有屏幕尺寸变化到720px前后,监听事件才会触发,性能嘎嘎好在项目中,当前组件卸载后,还可以将该监听事件卸载

mediaQuery.removeListener(listener);

好,使用方法就这些,很简单,就两个API:addListenerremoveListener

编写hook

设计思路:调用者传入监听的断点,然后hook返回布尔值,并且当屏幕变化至断点前后,布尔值的变化也会反应出来

//useMediaQuery.tsx

import {useEffect, useState} from 'react';

const breakPoints = {
  mobile: "(min-width: 480px)",
  ipad: "(min-width: 720px)",
  pc: "(min-width: 1080px)"
}

const useMediaQuery = (breakPointName: string)=>{
  const [matches, setMatches] = useState(false);

  useEffect(()=>{
    //if the breakPoint is error, then do nothing
    if(!breakPoints[breakPointName]) return;

    const breakPoint = breakPoints[breakPointName];
    const mediaQuery = window.matchMedia(breakPoint);
    setMatches(mediaQuery.matches);

    const handler = (e: any)=> setMatches(e.matches);
    mediaQuery.addListener(handler);
    return ()=>{
      mediaQuer.removeListener(handler);
    }
  },[]);

  return matches;
}

代码很简单,传入断点,返回对这个断点的媒体查询。并且会在组件卸载的时候,对监听事件的移除用法:

const isIpad = useMediaQuery('ipad');

console.log('isIpad: ',isIpad);

如果需要同时监听多个断点,就需要调用多次useMediaQuery,像这样

const isIpad = useMediaQuery('ipad');
const isMobile = useMediaQuery('mobile');
const isPC = useMediaQuery('pc');

这样需求很常见,需要对不同尺寸屏幕做不同的适应

改进

下面对这个hook做些改进,同时可以监听多个媒体查询

type keyBreakPoints = keyof typeof breakPoints;

const useMultipleMediaQuery = 
(breakPointNames: keyBreakPoints[]): Record<keyBreakPoints, boolean>=>{
  const [matches, setMatches] = useState(breakPointNames.reduce((res, nextItem)=>{
    res[nextItem] = false;
    return res;
  },{}));

  const setBreakPointsMatchs = (breakPointName: keyBreakPoints, isMatch: boolean)=>{
    matches[breakPointName] = isMatch;
    setMatches({...matches});
  }

  useEffect(()=>{
    try{
      const unSubScribes = breakPointNames.reduce((res, breakPointName)=>{
        const breakPoint = breakPoints[breakPointName];
        const mediaQuery = window.matchMedia(breakPoint);
        setBreakPointsMatchs(breakPointName, mediaQuery.matches);

        const handler = 
        (e: any)=> setBreakPointsMatchs(breakPointName, mediaQuery.matches);
        mediaQuery.addListener(handler);

        res.push(()=>medaiQuery.removeListener(handler));
        return res;
      },[]);
      return ()=>{
        unSubScribes.forEach(callback=>{
          callback && callback();
        })
      }
    }catch{
      return;
    }
  },[])

  return matches;
}

useMultipleMediaQuery支持了同时对多个断点进行媒体查询,通过传入一个断点的数组,并且返回的是一个Record<keyBreakPoints, boolean>类型的对象, 并且也会在组件卸载的时候,对所有的监听事件移除用法:

const { mobile, ipad, pc } = useMultipleMediaQuery(["mobile","ipad","pc"]);

console.log("is mobile: ", mobile);
console.log("is ipad: ", ipad);
console.log("is pc: ", pc);

总结:

这篇文文章介绍了matchMedia的用法,以及使用这个用法写了一个监听屏幕宽度的react hook,代码都是测试过的,可以直接拿来用。觉得有收获的小盆友可以点个赞哦✌️