Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Ray-56 opened this issue May 27, 2020 · 0 comments
Open

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

Ray-56 opened this issue May 27, 2020 · 0 comments
Labels
React React.js 相关

Comments

@Ray-56
Copy link
Owner

Ray-56 commented May 27, 2020

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

公司官网使用 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 响应式布局

@Ray-56 Ray-56 added the React React.js 相关 label May 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
React React.js 相关
Projects
None yet
Development

No branches or pull requests

1 participant