Gatsby 使用自定义 Hook 跳转移动端的方法总结

613 阅读2分钟

公司官网使用 Gatsby 开发,常见的开发模式有:

  • H5/PC 双站点并行开发 WebServer 获取浏览器的UserAgent信息切换站点
  • CSS 媒体查询响应式布局
  • React 中判断浏览器屏幕的大小渲染不同组件width > 720 ? <A /> : <B />

通篇采用Hooks方式如果不熟悉可以自行查看

由于业务情况 H5 就只有一个页面展示就使用了第三个模式,下面使用不同方式来实现

判断是否H5

UserAgent

function isH5() {
    const inMobile = window.location.href.match(/mobile/i) !== null;
    if (inMobile) return false;

    const sUserAgent = navigator.userAgent.toLowerCase();
    const bIsIpad = sUserAgent.match(/ipad/i) !== null;
    const bIsIphoneOs = sUserAgent.match(/iphone os/i) !== null;
    const bIsMidp = sUserAgent.match(/midp/i) !== null;
    const bIsUc7 = sUserAgent.match(/rv:1.2.3.4/i) !== null;
    const bIsUc = sUserAgent.match(/ucweb/i) !== null;
    const bIsAndroid = sUserAgent.match(/android/i) !== null;
    const bIsCE = sUserAgent.match(/windows ce/i) !== null;
    const bIsWM = sUserAgent.match(/windows mobile/i) !== null;

    if (bIsIpad || bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM) {
        return true;
    }
    return false;
}

innerWidth

const BREAK_POINT = 720;
function isH5(width) {
  // width 为 window.innerWidth 获取的
  return width < BREAK_POINT;
}

方法1: 使用 navigate

Gatsby 提供了 navigate 路由跳转,使用第一种判断是否为 H5 的方法,在 index.js 入口处判断跳转不同页面:

export default () => {
  useEffect(() => {
    navigate(isH5() ? '/mobile/' : '/home/', { replace: true });
  }, []);
  return null;
}

方法2: 使用自定义Hooks

方法2是这篇文章的重点,都使用第二种判断 H5 的方法。下面一步一步实现一个最优写法

1. 最简方法

export default () => {
  const width = window.innerWidth;
  return isH5(width) ? <H5 /> : <PC />;
}

当窗口调整大小时,未重新渲染对应组件

2. 加入 resize

export default () => {
  const [width, setWidth] = useState(window.innerWidth);
  const handleWindowResize = () => setWidth(window.innerWidth);
  useEffect(() => {
    window.addEventListener('resize', handleWindowResize);
    return () => window.removeEventListener('resize', handleWindowResize); // 销毁时移除事件,防止内存泄漏
  }, []);
  return isH5(width) ? <H5 /> : <PC />;
}

3. 加入 throttle

节流相关概念不做赘述,给出一个简易版 throttle:

function throttle(func, delay) {
    let end = 0;
    return function(...args) {
        const start = Date.now();
        if (start - end > delay) {
            end = start;
            func.apply(this, args);
        }
    };
}

修改代码为:

...
const handleWindowResize = throttle(() => setWidth(window.innerWidth), 300);
...

4. 构建 useViewport 自定义 Hook

使用自定义 Hook 可以将函数组件冗余代码复用

const useViewport = () => {
    const [width, setWidth] = useState(window.innerWidth);
		const handleWindowResize = throttle(() => setWidth(window.innerWidth), 300);
    useEffect(() => {        
        window.addEventListener('resize', handleWindowResize);
        return () => window.removeEventListener('resize', handleWindowResize);
    }, []);

    return { width };
}

将入口文件index.js代码精简为:

export default () => {
    const { width } = useViewport();
    return isH5(width) ? <H5 /> : <PC />;
}

5. 使用 Context 提升性能

如果项目中使用useViewport次数多会造成性能损耗,改为 Context 储存浏览器视口的宽高以及计算方法:

export const ViewportContext = createContext({});

const ViewportProvider = ({ children }) => {
    const [width, setWidth] = useState(window.innerWidth);
    const [height, setHeight] = useState(window.innerHeight);

    const handleWindowResize = throttle(() => {
        setWidth(window.innerWidth);
        setWidth(window.innerHeight);
    }, 300);

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

    return (
        <ViewportContext.Provider value={{ width, height }}>{children}</ViewportContext.Provider>
    );
};

入口文件index.js中加入ViewportProvider:

export default () => {
    const { width } = useViewport();
  	return <ViewportProvider>{isH5(width) ? <H5 /> : <PC />}</ViewportProvider>
}

useViewport改为:

const useViewport = () => {
    const { width, height } = React.useContext(viewportContext);

    return { width, height };
}

在使用useViewport时都是共享 Context 内数据

参考文章

React Hooks 响应式布局