React / Gatsby-根据屏幕尺寸渲染不同的组件

时间:2020-10-25 11:18:05

标签: html css reactjs graphql gatsby

(抬头警告:我对React / GraphQL并不是很熟悉,所以请放轻松我。)

我正在使用Gatsby构建博客,我的导航栏会根据屏幕尺寸更改其外观。我的导航栏中间是博客的徽标。现在,当读者在桌面上查看页面时,我希望显示完整的徽标(包括文本)。当他们在移动设备上查看时,我只希望呈现实际徽标(不包括文字)(我有一幅带有完整徽标的图像和一幅带有缩短徽标的图像)。

到目前为止,我的方法是1.创建两个单独的SiteNavLogo组件,并2.使用以下代码块检查当前窗口的大小(我删除了与该问题无关的所有代码,并将其替换了和“ ...”),并有条件地渲染两个徽标组件之一:

class SiteNav extends React.Component {
  ...
  };

  render(): JSX.Element {
    changeLogo = window.matchMedia('(max-width: 600px)').matches;
    return (
      <>
            ...
            {changeLogo && (
            <SiteNavLogoMobile />) || !changeLogo &&
            <SiteNavLogo/>}
      </>
    );
 }
}

这在gatsby develop上工作正常,但在gatsby build上失败,因为“窗口”不可用。在this文章中,我找到了使用componentDidMount的建议,所以我尝试了以下方法:

class SiteNav extends React.Component {
   changeLogo = false;
   componentDidMount(): void {
    this.changeLogo = window.matchMedia('(max-width: 600px)').matches;
  }
  };

  render(): JSX.Element {
    return (
      <>
            ...
            {this.changeLogo && (
            <SiteNavLogoMobile />) || !this.changeLogo &&
            <SiteNavLogo/>}
      </>
    );
 }
}

问题是在执行componentDidMount之前执行了第一个渲染周期,这意味着页面将始终使用changeLogo = false进行渲染。

如何做到这一点,以便移动用户查看页面时,可以显示出缩短的徽标? (我什至需要两个组件还是可以只更改GraphQL查询?) 任何建议都将受到高度赞赏。

3 个答案:

答案 0 :(得分:2)

您不应使用JS处理此问题,因为它总是会导致渲染不匹配。

例如,如果默认情况下,您在视口宽度小于400px时渲染<Logo>,但是用户以600px视口宽度访问您的网站,则他们会在React之前看到一小段错误的组件踢进来。

您可以通过检查window来解决此问题,并且仅在SSR中不呈现任何内容(这是对非SEO敏感内容的一种很好的方法),但是您首先失去了使用盖茨比的好处,搜索引擎看不到您的徽标,并且取决于布局的设置方式,用户仍可能会看到布局的变化。

如果仅使用CSS不能简单地修改您的组件,那么我建议您使用CSS切换器:

<div className="display-xs">
  <MobileVariant />
</div>

<div className="display-md">
  <DesktopVariant />
</div>

答案 1 :(得分:1)

尝试:

return (
  <>
        ...
        { typeof window !== 'undefined' 
          ? window.innerWidth <= 400 ? <SiteNavLogoMobile /> : <SiteNavLogo/> 
          : null
        }
  </>
);

当然,应该对代码进行重构,以避免链式三元条件(可读性差),但是我想展示这种方法的思想。如果定义了window,它将匹配第一个条件,根据innerWidth渲染一个或另一个组件(您可以将其更改为matchMedia),否则将不渲染直到代码获得窗口的null为止的所有内容(innerWidth

您可以将其添加到自定义函数中,而不是根据需要在JSX中呈现它。

此代码将随着窗口宽度的变化而触发。

答案 2 :(得分:0)

您应该使用组件的状态来存储changeLogo变量。通过使用状态变量,changeLogocomponentDidMount()中已正确更新,从而显示了正确的导航栏。

为确保changeLogo根据视口宽度进行更新,组件中还包含了addEventListener

class SiteNav extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      changeLogo: false
    };
    this.handleResize = this.handleResize.bind(this);
  }

  handleResize() {
    this.setState({
      changeLogo: window.matchMedia("(max-width: 400px)").matches
    });
  }

  componentDidMount() {
    window.addEventListener("resize", this.handleResize);
  }

  componentWillUnmount() {
    window.removeEventListener("resize", this.handleResize);
  }

  render() {
    return (
      <div>
        {this.state.changeLogo && <SiteNavLogoMobile />}
        {!this.state.changeLogo && <SiteNavLogo/>}
      </div>
    );
  }
} 

这是一个Codesandbox演示。