Medley Developer Blog

株式会社メドレーのエンジニア・デザイナーによるブログです

ComposableなUI設計を目指したフロントエンド開発

こんにちは、開発本部の舘野です。医療介護の求人サイト「ジョブメドレー」の開発を担当しています。

昨年、ジョブメドレーでは事業所が利用する採用管理画面のUIリニューアルを行いました。ユーザが使いやすいUIづくりを目指すために、長期間にわたり誰が開発しても一貫性あるUIを実現できるようなシステムが必要です。そこで今回は「Composable」なUIシステムの実現をテーマに、どのように開発を行ったのかについて、共有させていただきます。

背景:画面や機能追加のたびにUIの一貫性がなくなっていた

ジョブメドレーの採用管理画面とは、医療機関介護施設の採用担当者が求人情報の管理や応募者の選考状況の管理などを行う画面です。

この採用管理画面ですが、リニューアル以前はAngularフレームワークとして採用したSPAで、UIに関してはAngularUIのBootstrapを利用して、それぞれのエンジニアが実装を行っていました。

それなりのUIをスピーディーに実現できる点においては、BootsrapのようなUIフレームワークを利用することで受けられる恩恵は大きかったのですが、一方で、包括的にUI設計を行っているわけではなく、各人が局所的にUIを作っていくので、画面や機能を追加していく中で一貫性がないUIが増えていく状態になっていました。

実際にユーザインタビューなどを行ってみると、「ログインした後どうすれば良いのか分からない」、「〇〇という機能があることを今まで知らなかった」、「xxがどこにあるのか分からない」などの意見が多々あり、全面的なUIの見直しが必要になっていました。

医療や介護の現場での人材不足を解消するために採用担当者に提供するツールとして、今後さらに機能拡充していくことが求められていましたが、機能拡充していくことに耐えうる状態にはないというのがプロダクトチームのメンバーの共通認識でした。

そこで、全体的に情報設計から見直してデザインを刷新し、今後プロダクトを成長させていく上でスケール可能なUIを提供できるようにするため、UIリニューアルを決定しました。

フロントエンドで必要だったこと

Bootstrapを用いてエンジニアのみでUIを作っていたのとは異なり、リニューアルでは社内のデザイナーが現状のUI上の課題を整理したデザインを作成しました。

これに伴って、自前で全てのUIパーツを作成することになりましたが、Bootstrapに頼りきっていたときとは違い、堅牢性と柔軟性を伴ったUIシステムを自分たちで構築する必要がありました。

リニューアル前の採用管理画面のUIは一貫性に欠けており、ユーザは非常に多くの操作を学ぶ必要がありましたが、この責任はデザイナーだけでなくUI開発をするエンジニアにも大いにあります。 良いデザインができても、最終的にプロダクトのUIはコードによって作り上げられるものなので、エンジニア次第で一貫性に欠けるUIになってしまうことは十分にあり得ると思います。

往々にして起こり得るのは、目にする機会が比較的少ない画面であったり、改修対象ではない部分などが気づいたら崩れていたり、意図しないUIになってしまっていたりということですが、こういった状況に陥る大きな要因としてはフロントエンドの部分で一貫性に対する配慮ができてないことが1つだと思います。

そこで、すでにある採用管理画面を使いやすくするのはもちろん、今後スケールしていく中で一貫性のあるUIを担保し続けていくためには、リニューアルでフロントエンドも堅牢で柔軟なUIシステムへと変える必要がありました

UIリニューアルで開発上大切にしたこと

UIの一貫性を保つとなると、今のフロントエンドではもはや当然のことかもしれませんが、コンポーネント指向で構成することになると思います。

技術選定としては、上述の通りリニューアル以前はAngular(v1.4.11)を利用していましたが、リニューアルのタイミングでReactへ移行しました。

Reactを選択した理由としては、学習コストの点やコミュニティが活発でエコシステムが充実している点、単一方向のデータフロー、シンプルなAPIなどを総合的に判断してのものですが、目下の課題であるUIコンポーネントのメンテナビリティに関しても適切な選択肢であると考えました。

CSSの方はというと、リニューアル前はBootstrapでまかなえない部分はSassでそれぞれのエンジニアが書きたいように書くという状態でしたが、リニューアルでSassに加えて一部PostCSSという構成に変更して、設計はITCSS、Lintをstylelintで行う、という形にしました。

ITCSSを選択した背景としては、その詳細度順のレイヤー階層によってカスケードを管理しやすい点やレイヤーの増減で容易にスケールできる点などから選択しました。

CSS in JSも考慮はしましたが、リニューアルの時点ではこれという決定的な選択肢が無かったこともあり(まだstyled-componentsも正式リリースされてなかった)、classnamesを利用しました。

フレームワークやライブラリの選定も重要ですが、UIシステムを刷新する上で開発上最も重視したのは「Composability(コンポーネントの組み合わせが容易であること)」でした。

Composableであるということは、つまり様々な状況において組み込み可能な状態であり、再利用性が高いということになります。 それぞれのコンポーネントを組み合わせることが容易に出来るとともに、複数のコンポーネントを組み合わせた状態から1つ1つ分解することも容易な状態が望ましく、結果的にそれでUIが構築しやすく改修しやすい状態に自然となるはずです。

モーダルを例にあげると、モーダルの中で表示するコンテンツ要素やモーダルの背面に敷くオーバーレイコンポーネントは、モーダルコンポーネント自体には含まず別のコンポーネントとして切り出した方が再利用しやすく、組み合わせやすい、ということです。

<ModalFrame>
  <Modal>
    <ModalHead>...</ModalHead>
    <ModalBody>...</ModalBody>
  </Modal>
  <Overlay />
</ModalFrame>

上記の例でいうと、モーダルを画面の中央に配置することは<ModalFrame />が行い、<Modal />自体はモーダルに内包されるコンテンツのコンテナとしての役割だけを持ちます。<Overlay />も独立したコンポーネントの1つで、モーダル以外とも組み合わせて利用しています。

コンテナとなるコンポーネントとその子となるコンポーネントは、別コンポーネントに分離されていることで、お互いに依存しないようになります。

また、Sassファイルもこのコンポーネント構成に合わせて分けています。 ITCSSにおいて、<ModalFrame />のようなレイアウトのみの役割を持つ場合のスタイルはObjectsレイヤー(装飾を持たないUIパターンのレイヤー)となり、装飾を持つ<Modal /><Overlay />はComponentsレイヤーとして扱います。

@import “objects.modal-frame”;
@import “components.modal”;
@import “components.overlay”;

CSSはその特有のカスケードや詳細度によって決定されるスタイルがあり、依存関係を持たない状態を作ることが困難ですが、ITCSSの考えに則ってそれらのCSSの特徴に逆らわないように詳細度の低いものから順番に@importするようにしています。

Sassファイルの中身ですが、_objecst.modal-frame.scss<ModalFrame />のスタイルのみを記述するようにします。

.o-modal-frame {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: map-get($zIndexMap, modalFrame);
}

_components.modal.scssも同様に<Modal />のスタイルのみを記述します。

.c-modal {
  position: relative;
  margin: 0 auto;
  width: 900px;
  min-width: 640px;
  background-color: $JM-White;
  box-shadow: 0 1px 6px 0 rgba($JM-Black, 0.2);
  z-index: map-get($zIndexMap, modal);
}

このようにSassとReactコンポーネント毎に1対1の関係になるようにしています。 プレフィックスとして付与しているc-o-はITCSSのレイヤーのことを指します。 o-はObjectsレイヤーのプレフィックスで、c-はComponentsレイヤーのプレフィックスです。 基本的にReactのUIコンポーネント内では、コンポーネントの種別に応じてc-o-のプレフィックスを持つクラスと、状態によって付けたり外したりするStateレイヤーのs-プレフィックスのクラスのみを使用します。

話をReactに戻すと、下記のようなヘッダー要素を画面上部に固定表示するだけの役割を持つ<AppBar />コンポーネントは、props.childrenで子要素を受け取れるようになっているだけで、その内容には関知しないようになっています。

const AppBar = (props) => {
  return (
    <div className="o-app-bar">
      { props.children }
    </div>
  );
};

内包する子コンポーネントが何であれ、<AppBar />は自分自身の責任だけを果たせば良いので、開発上もシンプルに考えられます。

classNameに渡しているo-app-barはITCSSのObjectsレイヤーのクラスです。

.o-app-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: map-get($zIndexMap, appBar);
}

ヘッダー要素を画面上部に固定表示する、レイアウトのみの役割を持つコンポーネントなので、Objectsレイヤーとなり、o-app-barにはレイアウト目的のスタイルのみを持たせます。

ジョブメドレーの採用管理画面では、医療機関介護施設から求職者に向けた情報を入力していただくために多くのフォーム要素があり、非常に煩雑になりがちですが、それぞれの役割を果たすコンポーネントを組み合わせることで、UI開発上の堅牢性、柔軟性を高められるように努めました。

実際のリニューアル開発時には、全てのUIコンポーネントを実装する前に、開発側ではデザイナーが用意したSketchから、全てのUIパーツを洗い出す作業を行い、その中で分解不可能なレベルまでコンポーネントを分解していき、実装すべきコンポーネントを一覧化しました。

その後、作成したコンポーネント一覧から全UIコンポーネントをStorybookに実装していきました。

Storybookは、UIコンポーネント開発のサンドボックス環境として、ReactやVueを利用した開発では割と一般的に利用されるようになっていると思います。リニューアル時も各コンポーネントの開発環境として利用して、コンポーネントのパターンや組み合わせの確認などをStorybook上で行いました。

画面を作っていく段階では、用意したUIコンポーネントを組み合わせて利用すれば画面全体の大半のUIが出来上がるようになっていました。

細かい部分では、事前に用意するコンポーネントに不足があったり、実装した後で仕様の変更によりコンポーネント自体を削除することや、分解不可能な状態まで落とし込めてないコンポーネントが見つかったりと、様々な反省点はありました。ですがリニューアル全体を通して振り返ると、ComposableなUIコンポーネントで堅牢で柔軟性のある構成にするということに一定の成果は出せたかなと思います。

まとめ

UIリニューアル以降、採用管理画面ではリニューアル時のUIシステムを土台にして、継続的に機能を追加・改修しています。

プロダクトで設けているKPIも順調に遷移していて、顧客からの問い合わせもリニューアル以前のような、UI上の問題で利用が困難であるというものは減少し、ポジティブな結果が得られています。

開発をする上でもComposableになるようにコンポーネント群を作成したことで、リニューアル以降はUIの改修がシンプルに行えるようになり、開発メンバーのスキルセットに左右される部分が少なくなり、開発効率が上がりました

このような点からリニューアル自体は良かったと思うと同時に、一方でさらに良いUIを提供するために取り組むべきことは、少なくないと感じます。

例えば採用管理画面が十分にアクセシブルだとは言えないし、パフォーマンス面でもより一層の努力が求められます。もちろんUIコンポーネントの堅牢性もまだ十分とは言えません。

より良いプロダクトを提供するためにそういった課題に対しても継続して取り組んでいきたいと思います。

お知らせ

メドレーでは、エンジニア・デザイナーを募集しています。 メドレーでの開発にご興味ある方は、こちらをご覧ください。 www.medley.jp