使用 React Hooks 进行动态媒体查询

Dynamic media query with React Hooks

我正在尝试在 React 基础上构建一个 Bootstrap 网格(使用 ContainerRowcol-md-12 等)。我的第一个问题是 Container。在 Bootstrap 中,Container 有多个媒体查询。我可以(我将 Styled ComponentsStyled Tools 一起使用)显然可以创建多个媒体查询,这些媒体查询将相互覆盖,但我知道这不是最好的方法。

我正在考虑创建一种方法来检查用户 window.innerWidth 并将其更改为 useEffect 但我在开发自己的逻辑时遇到了一些问题,而且这是否会影响性能.这是我目前所拥有的。

import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import styled, { css } from "styled-components";
import { ifProp, ifNotProp } from "styled-tools";

const mediaQuerie = (minWidth, maxWidth) => {
  return `@media(min-width: ${minWidth}px) {
    max-width: ${maxWidth}px;
  }`;
};

const StyledContainer = styled.div`
  width: 100%;
  padding-right: 15px;
  padding-left: 15px;
  margin-right: auto;
  margin-left: auto;
  ${ifNotProp(
    "fluid",
    css`
        ${mediaQuerie(576, 540)}/* WRONG WAY USING MEDIA QUERIES */
        ${mediaQuerie(768, 720)}
        ${mediaQuerie(992, 960)}
        ${mediaQuerie(1200, 1140)}
      `
  )}
`;

const Container = props => {
  const [width, setWidth] = useState();

  setWidth(window.innerWidth);
  useEffect(() => {
    console.log(window.addEventListener("resize", width));
  }, [width]);

  return <StyledContainer {...props} />;
};

Container.propTypes = {
  children: PropTypes.node,
  fluid: PropTypes.bool
};
Container.defaultProps = {
  xs: 0,
  sm: 576,
  md: 768,
  lg: 992,
  xl: 1200
};

export default Container;

在我看来,针对不同的屏幕尺寸使用多个媒体查询总是更好。使用开箱即用的东西比自定义构建的逻辑具有性能提升。

如果您仍想采用相反的方式,即 使用 resize 侦听器 根据屏幕尺寸应用动态样式。您可以按如下方式调整代码。

确保只添加一次事件侦听器。为此,您的 useEffect 应更改为

useEffect(() => {
    setWidth(window.innerWidth);
    window.addEventListener("resize", () => setWidth(window.innerWidth));
  }, []);
  • 第一行setWidth(window.innerWidth);设置宽度 初始渲染,因为在那一刻调整大小事件永远不会 触发。
  • 第二行window.addEventListener("resize", () => setWidth(window.innerWidth));注册监听器更新状态(宽度)
  • 传递给useEffect的空数组[]确保事件监听器 只添加一次

现在创建一个接受宽度和 returns 样式道具的函数。例如,我创建了一个 function fluidMediaQuery,它向样式容器添加背景颜色。

const mediaQuerie = (minWidth, maxWidth, color) => {
  return `@media(min-width: ${minWidth}px) {
    max-width: ${maxWidth}px;
    background-color: ${color}
  }`;
};

const fluidMediaQuery = width => {
  if (width > 1200) {
    return mediaQuerie(1140, 1200, "green");
  } else if (width > 992) {
    return mediaQuerie(992, 960, "blue");
  } else if (width > 768) {
    return mediaQuerie(720, 768, "red");
  } else {
    return mediaQuerie(540, 576, "yellow");
  }
};

您的样式组件现在可以采用 fluid 道具并以不同方式呈现。

const StyledContainer = styled.div`
  width: 100%;
  height: 100px;
  padding-right: 15px;
  padding-left: 15px;
  margin-right: auto;
  margin-left: auto;
  ${ifNotProp(
    "fluid",
    css`
        ${mediaQuerie(540, 576)}/* WRONG WAY USING MEDIA QUERIES */
        ${mediaQuerie(720, 768)}
        ${mediaQuerie(960, 992)}
        ${mediaQuerie(1140, 1200)}
      `
  )}
  ${ifProp(
    "fluid",
    css`
      ${props => (props.width ? fluidMediaQuery(props.width) : "")}
    `
  )}
`;

最后渲染组件 <StyledContainer fluid={true} width={width} /> 这里的宽度来自状态,由调整大小事件侦听器设置。

完成的代码可能如下所示。尝试调整屏幕大小,您应该能够看到背景颜色发生变化。

import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import styled, { css } from "styled-components";
import { ifProp, ifNotProp } from "styled-tools";

const mediaQuerie = (minWidth, maxWidth, color) => {
  return `@media(min-width: ${minWidth}px) {
    max-width: ${maxWidth}px;
    background-color: ${color}
  }`;
};

const fluidMediaQuery = width => {
  if (width > 1200) {
    return mediaQuerie(1140, 1200, "green");
  } else if (width > 992) {
    return mediaQuerie(992, 960, "blue");
  } else if (width > 768) {
    return mediaQuerie(720, 768, "red");
  } else {
    return mediaQuerie(540, 576, "yellow");
  }
};

const StyledContainer = styled.div`
  width: 100%;
  height: 100px;
  padding-right: 15px;
  padding-left: 15px;
  margin-right: auto;
  margin-left: auto;
  ${ifNotProp(
    "fluid",
    css`
        ${mediaQuerie(540, 576)}/* WRONG WAY USING MEDIA QUERIES */
        ${mediaQuerie(720, 768)}
        ${mediaQuerie(960, 992)}
        ${mediaQuerie(1140, 1200)}
      `
  )}
  ${ifProp(
    "fluid",
    css`
      ${props => (props.width ? fluidMediaQuery(props.width) : "")}
    `
  )}
`;

const Container = props => {
  const [width, setWidth] = useState();
  useEffect(() => {
    setWidth(window.innerWidth);
    window.addEventListener("resize", () => setWidth(window.innerWidth));
  }, []);

  return (
    <div>
      <h4>Width of screen: {width}</h4>
      <StyledContainer fluid={true} width={width} />
    </div>
  );
};

Container.propTypes = {
  children: PropTypes.node,
  fluid: PropTypes.bool
};
Container.defaultProps = {
  xs: 0,
  sm: 576,
  md: 768,
  lg: 992,
  xl: 1200
};

export default Container;

重要说明。我不确定是否在所有情况下都会触发调整大小。例如,如果移动设备上的方向发生变化,是否会触发?您可能需要对这些方面进行一些研究。下面的 post 可能会有所帮助。

Will $(window).resize() fire on orientation change?