Reactでカスタムトグルスイッチを構築する方法

提供:Dev Guides
移動先:案内検索

序章

Webアプリケーションの構築には、通常、ユーザーとの対話のための準備が含まれます。 ユーザーとの対話を準備する重要な方法の1つは、フォームを使用することです。 ユーザーからさまざまな種類の入力を受け取るために、さまざまなフォームコンポーネントが存在します。 たとえば、パスワードコンポーネントは、ユーザーから機密情報を取得し、それが表示されないようにマスクします。

ほとんどの場合、ユーザーから取得する必要のある情報は boolean-like です。たとえば、yesまたはnotrueまたは falseenableまたはdisableonまたはoffなど。 従来、チェックボックスフォームコンポーネントは、これらの種類の入力を取得するために使用されます。 ただし、最近のインターフェイスデザインでは、アクセシビリティの問題がいくつかありますが、チェックボックスの代わりにトグルスイッチが一般的に使用されています。

このチュートリアルでは、Reactを使用してカスタムトグルスイッチコンポーネントを構築する方法を説明します。 チュートリアルの最後に、カスタムトグルスイッチコンポーネントを使用するデモReactアプリがあります。

これは、このチュートリアルで構築する最終的なアプリケーションのデモです。

前提条件

始める前に、次のものが必要です。

  • Node.jsおよびnpmバージョン5.2以降がマシンにインストールされています。 Nodeをインストールしてnpmのバージョンを確認するには、ご使用の環境のNode.jsのインストール方法とローカル開発環境の作成方法ガイドを参照してください。 npm 5.2以降を使用すると、npxコマンドを利用できるようになります。 npxを使用すると、パッケージをグローバルにダウンロードせずにcreate-react-appを実行できます。
  • このチュートリアルは、あなたがすでにReactに精通していることを前提としています。 そうでない場合は、 React.js チュートリアルシリーズのコーディング方法を確認するか、Reactドキュメントを読んでReactの詳細を確認してください。

ステップ1—はじめに

開始するには、npxcreate-react-appを使用して新しいReactアプリケーションを作成します。 アプリケーションには任意の名前を付けることができますが、このチュートリアルではreact-toggle-switchを使用します。

npx create-react-app react-toggle-switch

次に、アプリケーションに必要な依存関係をインストールします。 ターミナルウィンドウを使用して、プロジェクトディレクトリに移動します。

cd react-toggle-switch

次のコマンドを実行して、必要な依存関係をインストールします。

npm install [email protected] [email protected] [email protected] [email protected] [email protected]

注:最小サポートのクイックガイドを参照して、インストールするnode-sassのバージョンがご使用の環境と互換性があることを確認してください。


デフォルトのスタイルが必要になるため、アプリケーションの依存関係としてbootstrapパッケージをインストールしました。 アプリケーションにブートストラップを含めるには、src/index.jsファイルを編集し、他のすべてのimportステートメントの前に次の行を追加します。

src / index.js

import "bootstrap/dist/css/bootstrap.min.css";

npmを指定して次のコマンドを実行し、アプリケーションを起動します。

npm start

アプリケーションを起動すると、開発を開始できます。 ライブリロード機能を備えたブラウザタブが開いていることに注意してください。 ライブリロードは、開発中にアプリケーションへの変更と同期し続けます。

この時点で、アプリケーションビューは次のスクリーンショットのようになります。

次に、トグルコンポーネントを作成します。

ステップ2—ToggleSwitchコンポーネントを作成する

コンポーネントをビルドする前に、プロジェクトのsrcディレクトリ内にcomponentsという名前の新しいディレクトリを作成します。

mkdir -p src/components

次に、componentsディレクトリ内にToggleSwitchという名前の別の新しいディレクトリを作成します。

mkdir -p src/components/ToggleSwitch

src/components/ToggleSwitch内に、index.jsindex.scssの2つの新しいファイルを作成します。 index.jsファイルを作成して、お気に入りのテキストエディタで開きます。

nano src/components/ToggleSwitch/index.js

次のコンテンツをsrc/components/ToggleSwitch/index.jsファイルに追加します。

src / components / ToggleSwitch / index.js

import PropTypes from 'prop-types';
import classnames from 'classnames';
import isString from 'lodash/isString';
import React, { Component } from 'react';
import isBoolean from 'lodash/isBoolean';
import isFunction from 'lodash/isFunction';
import './index.scss';

class ToggleSwitch extends Component {}

ToggleSwitch.propTypes = {
  theme: PropTypes.string,
  enabled: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.func
  ]),
  onStateChanged: PropTypes.func
}

export default ToggleSwitch;

このコードスニペットでは、ToggleSwitchコンポーネントを作成し、その小道具のいくつかに型チェックを追加しました。

  • theme:トグルスイッチのスタイルと色を示すstringです。
  • enabledbooleanまたはbooleanを返すfunctionのいずれかで、レンダリング時のトグルスイッチの状態を決定します。
  • onStateChanged:トグルスイッチの状態が変化したときに呼び出されるコールバック関数です。 これは、スイッチが切り替えられたときに親コンポーネントでアクションをトリガーする場合に役立ちます。

ToggleSwitch状態の初期化

次のコードスニペットでは、ToggleSwitchコンポーネントの状態を初期化し、トグルスイッチの状態を取得するためのいくつかのコンポーネントメソッドを定義します。

src / components / ToggleSwitch / index.js

// ...

class ToggleSwitch extends Component {
  state = { enabled: this.enabledFromProps() }

  isEnabled = () => this.state.enabled

  enabledFromProps() {
    let { enabled } = this.props;

    // If enabled is a function, invoke the function
    enabled = isFunction(enabled) ? enabled() : enabled;

    // Return enabled if it is a boolean, otherwise false
    return isBoolean(enabled) && enabled;
  }
}

// ...

ここで、enabledFromProps()メソッドは、渡されたenabled小道具を解決し、レンダリング時にスイッチを有効にする必要があるかどうかを示すbooleanを返します。 enabledpropがbooleanの場合、ブール値を返します。 functionの場合、戻り値がbooleanであるかどうかを判断する前に、まず関数を呼び出します。 それ以外の場合は、falseを返します。

enabledFromProps()からの戻り値を使用して初期のenabled状態を設定したことに注意してください。 また、isEnabled()メソッドを追加して、現在のenabled状態を取得しました。

ToggleSwitchの切り替え

先に進んで、クリックされたときにスイッチを切り替えるメソッドを追加しましょう。 次のコードをファイルに追加します。

src / components / ToggleSwitch / index.js

// ...

class ToggleSwitch extends Component {

  // ...other class members here

  toggleSwitch = evt => {
    evt.persist();
    evt.preventDefault();

    const { onClick, onStateChanged } = this.props;

    this.setState({ enabled: !this.state.enabled }, () => {
      const state = this.state;

      // Augument the event object with SWITCH_STATE
      const switchEvent = Object.assign(evt, { SWITCH_STATE: state });

      // Execute the callback functions
      isFunction(onClick) && onClick(switchEvent);
      isFunction(onStateChanged) && onStateChanged(state);
    });
  }
}

// ...

このメソッドはclickイベントリスナーとしてトリガーされるため、evtパラメーターを使用して宣言しました。 まず、このメソッドは、論理NOT!)演算子を使用して、現在のenabled状態を切り替えます。 状態が更新されると、onClickおよびonStateChangedプロップに渡されるコールバック関数がトリガーされます。

onClickは最初の引数としてイベントを必要とするため、新しい状態オブジェクトを含む追加のSWITCH_STATEプロパティでイベントを拡張したことに注意してください。 ただし、onStateChangedコールバックは、新しい状態オブジェクトで呼び出されます。

ToggleSwitchのレンダリング

最後に、ToggleSwitchコンポーネントのrender()メソッドを実装しましょう。 次のコードをファイルに追加します。

src / components / ToggleSwitch / index.js

// ...

class ToggleSwitch extends Component {

  // ...other class members here

  render() {
    const { enabled } = this.state;

    // Isolate special props and store the remaining as restProps
    const { enabled: _enabled, theme, onClick, className, onStateChanged, ...restProps } = this.props;

    // Use default as a fallback theme if valid theme is not passed
    const switchTheme = (theme && isString(theme)) ? theme : 'default';

    const switchClasses = classnames(
      `switch switch--${switchTheme}`,
      className
    )

    const togglerClasses = classnames(
      'switch-toggle',
      `switch-toggle--${enabled ? 'on' : 'off'}`
    )

    return (
      <div className={switchClasses} onClick={this.toggleSwitch} {...restProps}>
        <div className={togglerClasses}></div>
      </div>
    )
  }
}

// ...

このrender()メソッドでは多くのことが行われているので、少し分解してみましょう。

  1. まず、enabled状態がコンポーネント状態から分解されます。
  2. 次に、コンポーネントの小道具を分解し、スイッチに渡されるrestPropsを抽出します。 これにより、コンポーネントの特別な小道具を傍受して分離することができます。
  3. 次に、 classnames を使用して、コンポーネントのthemeenabledの状態に基づいて、スイッチと内部トグルのクラスを作成します。
  4. 最後に、適切な小道具とクラスを使用してDOM要素をレンダリングします。 スイッチのclickイベントリスナーとしてthis.toggleSwitchを渡したことに注意してください。

ファイルを保存して閉じます。

これで、ToggleSwitchが作成されました。

ステップ3—ToggleSwitchのスタイリング

ToggleSwitchコンポーネントとそれに必要な機能が揃ったので、先に進んでそのスタイルを記述できます。

お気に入りのテキストエディタでindex.scssファイルを開きます。

nano src/components/ToggleSwitch/index.scss

次のコードスニペットをファイルに追加します。

src / components / ToggleSwitch / index.scss

// DEFAULT COLOR VARIABLES

$ball-color: #ffffff;
$active-color: #62c28e;
$inactive-color: #cccccc;

// DEFAULT SIZING VARIABLES

$switch-size: 32px;
$ball-spacing: 2px;
$stretch-factor: 1.625;

// DEFAULT CLASS VARIABLE

$switch-class: 'switch-toggle';


/* SWITCH MIXIN */

@mixin switch($size: $switch-size, $spacing: $ball-spacing, $stretch: $stretch-factor, $color: $active-color, $class: $switch-class) {}

ここでは、いくつかのデフォルト変数を定義し、switchミックスインを作成しました。 次のセクションでは、ミックスインを実装しますが、最初に、switchミックスインのパラメーターを調べてみましょう。

  • $size:スイッチ要素の高さ。 長さの単位が必要です。 デフォルトは32pxです。
  • $spacing:丸いボールとスイッチコンテナの間のスペース。 長さの単位が必要です。 デフォルトは2pxです。
  • $stretch:スイッチ要素の幅を伸ばす範囲を決定するために使用される係数。 単位のない数でなければなりません。 デフォルトは1.625です。
  • $color:アクティブ状態のときのスイッチの色。 これは有効な色の値である必要があります。 この色に関係なく、円形のボールは常に白であることに注意してください。
  • $class:スイッチを識別するための基本クラス。 これは、スイッチの状態クラスを動的に作成するために使用されます。 デフォルトは'switch-toggle'です。 したがって、デフォルトの状態クラスは.switch-toggle--onおよび.switch-toggle--offです。

スイッチミックスインの実装

switchミックスインの実装は次のとおりです。

src / components / ToggleSwitch / index.scss

// ...

@mixin switch($size: $switch-size, $spacing: $ball-spacing, $stretch: $stretch-factor, $color: $active-color, $class: $switch-class) {

  // SELECTOR VARIABLES

  $self: '.' + $class;
  $on: #{$self}--on;
  $off: #{$self}--off;

  // SWITCH VARIABLES

  $active-color: $color;
  $switch-size: $size;
  $ball-spacing: $spacing;
  $stretch-factor: $stretch;
  $ball-size: $switch-size - ($ball-spacing * 2);
  $ball-slide-size: ($switch-size * ($stretch-factor - 1) + $ball-spacing);

  // SWITCH STYLES

  height: $switch-size;
  width: $switch-size * $stretch-factor;
  cursor: pointer !important;
  user-select: none !important;
  position: relative !important;
  display: inline-block;

  &#{$on},
  &#{$off} {
    &::before,
    &::after {
      content: '';
      left: 0;
      position: absolute !important;
    }

    &::before {
      height: inherit;
      width: inherit;
      border-radius: $switch-size / 2;
      will-change: background;
      transition: background .4s .3s ease-out;
    }

    &::after {
      top: $ball-spacing;
      height: $ball-size;
      width: $ball-size;
      border-radius: $ball-size / 2;
      background: $ball-color !important;
      will-change: transform;
      transition: transform .4s ease-out;
    }
  }

  &#{$on} {
    &::before {
      background: $active-color !important;
    }
    &::after {
      transform: translateX($ball-slide-size);
    }
  }

  &#{$off} {
    &::before {
      background: $inactive-color !important;
    }
    &::after {
      transform: translateX($ball-spacing);
    }
  }

}

このミックスインでは、ミックスインに渡されたパラメーターに基づいていくつかの変数を設定することから始めます。 次に、スタイルを作成します。 ::afterおよび::before疑似要素を使用して、スイッチのコンポーネントを動的に作成していることに注意してください。 ::beforeはスイッチコンテナを作成し、::afterは円形ボールを作成します。

また、基本クラスから状態クラスを構築し、それらを変数に割り当てる方法にも注目してください。 $on変数は有効状態のセレクターにマップされ、$off変数は無効状態のセレクターにマップされます。

また、スタイルを使用できるようにするには、基本クラス(.switch-toggle)を状態クラス(.switch-toggle--onまたは.switch-toggle--off)と一緒に使用する必要があることを確認しました。 したがって、&#{$on}および&#{$off}セレクターを使用しました。

テーマスイッチの作成

switchミックスインができたので、トグルスイッチ用にいくつかのテーマスタイルを作成し続けます。 defaultgraphite-smallの2つのテーマを作成します。

次のコードスニペットをsrc/components/ToggleSwitch/index.scssファイルに追加します。

src / components / ToggleSwitch / index.scss

// ...

@function get-switch-class($selector) {

  // First parse the selector using `selector-parse`
  // Extract the first selector in the first list using `nth` twice
  // Extract the first simple selector using `simple-selectors` and `nth`
  // Extract the class name using `str-slice`

  @return str-slice(nth(simple-selectors(nth(nth(selector-parse($selector), 1), 1)), 1), 2);

}

.switch {
  $self: &;
  $toggle: #{$self}-toggle;
  $class: get-switch-class($toggle);

  // default theme
  &#{$self}--default > #{$toggle} {

    // Always pass the $class to the mixin
    @include switch($class: $class);

  }

  // graphite-small theme
  &#{$self}--graphite-small > #{$toggle} {

    // A smaller switch with a `gray` active color
    // Always pass the $class to the mixin
    @include switch($color: gray, $size: 20px, $class: $class);

  }
}

ここでは、最初に、$selectorをパラメーターとして受け取るget-switch-classという名前のSass関数を作成します。 $selectorをSass関数のチェーンを介して実行し、ファーストクラス名を抽出しようとします。 たとえば、次を受け取った場合:

  • .class-1 .class-2, .class-3 .class-4class-1を返します。
  • .class-5.class-6 > .class-7.class-8class-5を返します。

次に、.switchクラスのスタイルを定義します。 トグルクラスを動的に.switch-toggleに設定し、それを$toggle変数に割り当てます。 get-switch-class()関数呼び出しから返されたクラス名を$class変数に割り当てることに注意してください。 最後に、テーマクラスを作成するために必要なパラメータとともにswitchミックスインを含めます。

テーマスイッチのセレクターの構造は次のようになっていることに注意してください:&#{$self}--default > #{$toggle}デフォルトのテーマを例として使用)。 すべてをまとめると、これは、スタイルを適用するために、要素の階層が次のようになる必要があることを意味します。

<!-- Use the default theme: switch--default  -->
<element class="switch switch--default">

  <!-- The switch is in enabled state: switch-toggle--on -->
  <element class="switch-toggle switch-toggle--on"></element>

</element>

これは、トグルスイッチのテーマがどのように見えるかを示すデモです。

ステップ4—サンプルアプリを構築する

ToggleSwitch Reactコンポーネントに必要なスタイルを設定したので、先に進んで、チュートリアルの最初に見たサンプルアプリの作成を開始しましょう。

src/App.jsファイルを次のコードスニペットのように変更します。

src / App.js

import classnames from 'classnames';
import snakeCase from 'lodash/snakeCase';
import React, { Component } from 'react';
import Switch from './components/ToggleSwitch';
import './App.css';

// List of activities that can trigger notifications
const ACTIVITIES = [
  'News Feeds', 'Likes and Comments', 'Live Stream', 'Upcoming Events',
  'Friend Requests', 'Nearby Friends', 'Birthdays', 'Account Sign-In'
];

class App extends Component {

  // Initialize app state, all activities are enabled by default
  state = { enabled: false, only: ACTIVITIES.map(snakeCase) }

  toggleNotifications = ({ enabled }) => {
    const { only } = this.state;
    this.setState({ enabled, only: enabled ? only : ACTIVITIES.map(snakeCase) });
  }

  render() {
    const { enabled } = this.state;

    const headingClasses = classnames(
      'font-weight-light h2 mb-0 pl-4',
      enabled ? 'text-dark' : 'text-secondary'
    );

    return (
      <div className="App position-absolute text-left d-flex justify-content-center align-items-start pt-5 h-100 w-100">
        <div className="d-flex flex-wrap mt-5" style={{width: 600}}>

          <div className="d-flex p-4 border rounded align-items-center w-100">
            <Switch theme="default"
              className="d-flex"
              enabled={enabled}
              onStateChanged={this.toggleNotifications}
            />

            <span className={headingClasses}>Notifications</span>
          </div>

          {/* ... Notification options here ... */}

        </div>
      </div>
    );
  }

}

export default App;

ここでは、通知をトリガーできる一連のアクティビティを使用してACTIVITIES定数を初期化します。 次に、2つのプロパティを使用してアプリの状態を初期化しました。

  • enabled:通知が有効かどうかを示すboolean
  • only:通知をトリガーするために有効になっているすべてのアクティビティを含むarray

LodashsnakeCaseユーティリティを使用して、状態を更新する前にアクティビティをスネークケースに変換しました。 したがって、'News Feeds''news_feeds'になります。

次に、通知スイッチから受信した状態に基づいてアプリの状態を更新するtoggleNotifications()メソッドを定義しました。 これは、トグルスイッチのonStateChangedプロップに渡されるコールバック関数として使用されます。 only状態プロパティにはすべてのアクティビティが入力されているため、アプリを有効にすると、すべてのアクティビティがデフォルトで有効になることに注意してください。

最後に、アプリのDOM要素をレンダリングし、通知オプション用のスロットを残しました。これはまもなく追加されます。 この時点で、アプリは次のスクリーンショットのようになります。

次に、先に進んで、このコメントがある行を探します。

{/* ... Notification options here ... */}

通知オプションを表示するには、次のコンテンツに置き換えます。

src / App.js

// ...

{ enabled && (

  <div className="w-100 mt-5">
    <div className="container-fluid px-0">

      <div className="pt-5">
        <div className="d-flex justify-content-between align-items-center">
          <span className="d-block font-weight-bold text-secondary small">Email Address</span>
          <span className="text-secondary small mb-1 d-block">
            <small>Provide a valid email address with which to receive notifications.</small>
          </span>
        </div>

        <div className="mt-2">
          <input type="text" placeholder="[email protected]" className="form-control" style={{ fontSize: 14 }} />
        </div>
      </div>

      <div className="pt-5 mt-4">
        <div className="d-flex justify-content-between align-items-center border-bottom pb-2">
          <span className="d-block font-weight-bold text-secondary small">Filter Notifications</span>
          <span className="text-secondary small mb-1 d-block">
            <small>Select the account activities for which to receive notifications.</small>
          </span>
        </div>

        <div className="mt-5">
          <div className="row flex-column align-content-start" style={{ maxHeight: 180 }}>
            { this.renderNotifiableActivities() }
          </div>
        </div>
      </div>

    </div>
  </div>

) }

this.renderNotifiableActivities()を呼び出してアクティビティをレンダリングしたことに気付くかもしれません。 先に進んで、このメソッドと他の残りのメソッドを実装しましょう。

Appコンポーネントに次のメソッドを追加します。

src / App.js

// ...

class App extends Component {
  // ...

  toggleActivityEnabled = activity => ({ enabled }) => {
    let { only } = this.state;

    if (enabled && !only.includes(activity)) {
      only.push(activity);
      return this.setState({ only });
    }

    if (!enabled && only.includes(activity)) {
      only = only.filter(item => item !== activity);
      return this.setState({ only });
    }
  }

  renderNotifiableActivities() {
    const { only } = this.state;

    return ACTIVITIES.map((activity, index) => {
      const key = snakeCase(activity);
      const enabled = only.includes(key);

      const activityClasses = classnames(
        'small mb-0 pl-3',
        enabled ? 'text-dark' : 'text-secondary'
      );

      return (
        <div key={index} className="col-5 d-flex mb-3">
          <Switch theme="graphite-small"
            className="d-flex"
            enabled={enabled}
            onStateChanged={ this.toggleActivityEnabled(key) }
          />

          <span className={activityClasses}>{ activity }</span>
        </div>
      );
    })
  }

  // ...
}

ここでは、renderNotifiableActivitiesメソッドを実装しました。 ACTIVITIES.map()を使用してすべてのアクティビティを繰り返し、それぞれにトグルスイッチを使用してレンダリングします。 トグルスイッチがgraphite-smallテーマを使用していることに注意してください。 また、各アクティビティのenabled状態は、only状態変数にすでに存在するかどうかを確認することで検出されます。

最後に、各アクティビティのトグルスイッチのonStateChanged小道具にコールバック関数を提供するために使用されるtoggleActivityEnabledメソッドを定義しました。 アクティビティを引数として渡し、コールバック関数を返すことができるように、高階関数として定義しました。 アクティビティがすでに有効になっているかどうかを確認し、それに応じて状態を更新します。

これで、アプリは次のスクリーンショットのようになります。

最初のスクリーンショットに示されているように有効にするのではなく、デフォルトですべてのアクティビティを無効にしたい場合は、Appコンポーネントに次の変更を加えることができます。

[src/App.js]
// ...

class App extends Component {

  // Initialize app state, all activities are disabled by default
  state = { enabled: false, only: [] }

  toggleNotifications = ({ enabled }) => {
    const { only } = this.state;
    this.setState({ enabled, only: enabled ? only : [] });
  }
}

このステップで、トグルスイッチの作成が完了しました。 次のステップでは、アプリケーションへのアクセシビリティを改善する方法を学びます。

ステップ5—アクセシビリティの懸念に対処する

従来のチェックボックスの代わりにアプリケーションでトグルスイッチを使用すると、特に従来のチェックボックスを好きなようにスタイル設定するのが難しいため、よりすっきりとしたインターフェイスを作成できます。

ただし、チェックボックスの代わりにトグルスイッチを使用すると、ユーザーエージェントがコンポーネントの機能を正しく解釈できない可能性があるため、アクセシビリティの問題が発生します。

トグルスイッチのアクセシビリティを改善し、ユーザーエージェントが役割を正しく理解できるようにするために、いくつかのことができます。 たとえば、次のARIA属性を使用できます。

<switch-element tabindex="0" role="switch" aria-checked="true" aria-labelledby="#label-element"></switch-element>

トグルスイッチでさらに多くのイベントをリッスンして、ユーザーがコンポーネントを操作する方法を増やすこともできます。

結論

このチュートリアルでは、さまざまなテーマをサポートする適切なスタイルでReactアプリケーション用のカスタムトグルスイッチを作成しました。 従来のチェックボックスの代わりに、アプリケーションでそれを使用する方法を検討しました。 さらに、関連するアクセシビリティの懸念と、改善を行うために何ができるかを調査しました。

このチュートリアルの完全なソースコードについては、GitHubのreact-toggle-switch-demoリポジトリを確認してください。 また、CodeSandboxでこのチュートリアルのライブデモを入手することもできます。