Islands in the byte stream

Technical notes by a software engineer

Rails + React + SSRのもとでレスポンシブにするにあたって現状とっている方法

SSRと絡めようとすると難しくて、いろいろ試行錯誤しました。いまはこういう方向でやろうとしていて、そこそこメンテナンス可能に書けそうだなという手応えがあります。

三行で

  • CSSのメディアクエリのみを使い、Railsでテンプレートエンジンを使うにせよReactでSSRするにせよReactでCSRするにせよ同じ方法を使う
  • PCのみ表示させる場合は .showPcOnly クラスを、スマホでのみ表示させる場合は.showSmartphoneOnlyクラスをつけたdiv囲む
  • たまに .showPcOnly などとは別に width: 100% などを指定しないといけないが、それは別途クラスを定義して与える

ヘルパーSCSSクラス

こんな感じのヘルパーを用意しておく。

$smartphoneBreakpoint: 767px;

@mixin smartphone {
  @media screen and (max-width: $smartphoneBreakpoint) {
    @content;
  }
}

@mixin pc {
  @media screen and (min-width: $smartphoneBreakpoint + 1) {
    @content;
  }
}

@include smartphone {
  .showPcOnly {
    display: none;
  }
}

@include pc {
  .showSmartphoneOnly {
    display: none;
  }
}

ヘルパーJSX components

こんな感じのJSX componentsを定義しておく。

import React from 'react';
import classNames from 'classnames';

export class SmartphoneOnly extends React.Component {
  static propTypes() {
    return {
      children: React.PropTypes.any.isRequired,
      className: React.PropTypes.string,
    };
  }

  render() {
    return (<div className={classNames(this.props.className, "showSmartphoneOnly")}>
          {this.props.children}
      </div>);
  }
}

export class PcOnly extends React.Component {
  static propTypes() {
    return {
      children: React.PropTypes.any.isRequired,
      className: React.PropTypes.string,
    };
  }

  render() {
    return (<div className={classNames(this.props.className, "showPcOnly")}>
            {this.props.children}
        </div>);
  }
}

試したこと

  • .showPcOnly クラスを@extendする→❌
    • そのクラスにdisplay: flexをつけたい場合に衝突してうまくいかない
  • react-responsive ライブラリをつかう→❌
    • サーバーサイドでJavaScriptのmedia query APIを使えないのでダメ
    • JS media queryのための情報をクライアントから送る(e.g. width: 1024)こともできるが所詮シミュレーションでしかないし、キャッシュとの相性も悪い
  • CSSでクラスごとに地道にがんばる→❌
    • DOM構造をみて(=hamlやJSXをみて)どうスイッチされるかを把握できないのでつらい
    • DOM構造に直接.showPcOnly<ShowPcOnly>で書くのがわかりやすくていい