公司官网使用 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 内数据