Svelte で Presentational and Container Components コンポーネント設計をする


はじめに

Svelteにはスタイルガイドが存在しない。

Svelteを始めたいけど躊躇している方の中には、スタイルガイドやベストプラクティスが無いことも不安になる一因な気がしています。今回はReactの設計手法として知られているPresentational and Container ComponentsSvelteに流用できないか試してみました。

結論

Presentational and Container ComponentsSvelteでも導入メリットがあります。Svelteにはスタイルガイドやベストプラクティスがまだありませんが、ReactVueの設計手法を取り入れることが出来ます。

想定読者

  • Svelteのベストプラクティスを集めいている方
  • Svelteを触ってみたい方(Githubにコードを上げているので手元で確認できます)
  • Presentational and Container Componentsというコンポーネント設計を知りたい方

サンプルコードの紹介

環境

  • "svelte": "^3.0.0",
  • "typescript": "^3.9.3"

こんなの作りました

アドベントカレンダーということで、クリスマスツリーを飾り付けするゲームを作りました。

Presentational and Container Components コンポーネント設計

今回のフォルダ構成はBASE株式会社さんの資料「BASEにおける Vueコンポーネント設計の現在」1を参考にさせていただきました。ありがとうございます!

資料をもとに作成したフォルダ構成です。(今は"ふ〜ん そうなんだ"くらいの気持ちで見てください。)

Presentational and Container Components とは

Presentational and Container ComponentsとはReactでの設計手法で、一つのコンポーネントを見た目のコンポーネントとロジックのコンポーネントに分割します。
この設計手法のメリットは以下の通りです。

  1. 見た目とロジックの責務を分離できる(関心の分離)
  2. デザイナーの方がロジック処理を触ることなくスタイル調整できる
  3. 見た目のコンポーネントの再利用性が向上する
  4. アトミックデザインでのlayoutコンポーネントが作りやすくなる ※今回は言及しません

余談になりますが、この設計手法を明文化したDan Abramovさんは、今ではこの設計手法を推奨しておらず、ロジックの分離はReactCustom Hooksを使うことで同じ効果が得られる2と言っています。SvelteにはCustom Hooksのようなeffect(副作用)を制御しきれるものは無い認識なのでこちらは割愛いたします。

実装方法

以下の赤枠のコンポーネントが対象です。

名前から想像がつくと思いますが

  • ChristmasTree.svelte
    • Container Components(ロジック)です。
  • ChristmasTreePresentation.svelte
    • Presentational Components(見た目)です。

処理の流れ

Presentational and Container Components思想のコンポーネントの処理の流れはContainer Componentsでロジック処理(storeへのアクセスや関数)を記述し、それらをPresentational Componentsに引き渡します。

実際のコード

Container Components(ロジック)はこんな感じです。

ChristmasTree.svelte(ロジック)
<script lang="ts">
  import ChristmasTreePresentation from "./ChristmasTreePresentation.svelte";
  import { ornament } from "../../store/ornament";
  import { treeOrnaments } from "../../store/treeOrnaments";
  ...省略の意味以下同じ

  // ベルのボタンのクリック処理
  function handleClickBell() {
    ornament.set({ type: "bell", colorHex: DEFAULT_BELL_COLOR });
  }

  // ベルボタンがアクティブか?
  let isActiveBell = false;

  // 飾り付けのstore
  let allOrnaments: TreeOrnament[] = [];
  treeOrnaments.subscribe((currentTreeOrnaments) => {
    allOrnaments = currentTreeOrnaments;
  });

  ...
</script>

<!-- Presentational Component に props を渡すだけ -->
<ChristmasTreePresentation
  {handleClickBell}
  {isActiveBell}
  {allOrnaments}
  ... />

一方、Presentational Components(見た目)はこんな感じです。

ChristmasTreePresentation.svelte(見た目)
<script lang="ts">
  ...
  // Container Component からの props を受け取る
  // export let 〜 で宣言するとコンポーネントの引数になる。Svelte簡単!!
  export let handleClickBell: () => void;   // イベント処理
  export let isActiveBell: boolean;         // ボタンの非活性
  export let allOrnaments: TreeOrnament[];  // 全ての飾り付け
  ...
</script>

<!-- 見た目に関することだけ書いてあるのでHTML構造が分かりやすくなる -->
<Header />
<div class="mx-1 my-2">
  <!-- 受け取った関数や変数をセットするだけ -->
  <AppButton on:click={handleClickBell} color="red" isActive={isActiveBell}>
    <AppIcon id="bell" />
  </AppButton>
  ...
</div>
<div class="h-full rounded-lg bg-indigo-900 mx-2 mb-1">
  <AppCanvas on:click={(event) => handleClickCanvas(event)}>
    <!-- svelteはテンプレート構文 #each を使えます -->
    {#each allOrnaments as ornament (ornament.id)}
      <AppIcon ... />  // 省略してます
    {/each}
  </AppCanvas>
</div>
<Footer />

いかがでしょうか?
メリットである「1. 見た目とロジックの責務を分離できる(関心の分離)」が少しでも伝わっていたら嬉しいです。

メリット

メリット解説① 〜開発とデザイナーの実装箇所の分離〜

1.見た目とロジックの責務を分離できる(関心の分離)
2.デザイナーの方がロジック処理を触ることなくスタイル調整できる

Presentational and Container Componentsの設計手法を取り入れたことにより、開発はContainer Componentを、デザイナーはPresentational Componentを主に実装していくことになります。

実際の運用も考慮すると、変更対象ファイルがそのまま変更理由になるため、コードレビューもしやすくなると思われます。

たとえば、デザイナーの方がContainer Componentを変更していたら、誤ってロジックを変更していたり、見た目を変更したかったにも関わらず、Container Componentを修正してしまっているかもしれません。逆もしかり。

メリット解説② 〜コンポーネント管理のしやすさ〜

3.見た目のコンポーネントの再利用性が向上する

Presentational and Container Componentsには

  • Container Componentsはステートフル(storeにアクセスする等)
  • Presentational Componentsはステートレス

という概念があります。

もしコンポーネントにstoreのロジック処理が書いてあった場合、コンポーネント設計の要である再利用性は著しく低下するでしょう。

Storybookを導入されている場合、コンポーネント管理もしずらくなるでしょうし、便利なアドオン Knobs3 などでは、動的にpropsを変更して見た目を確認やすくなるでしょう。( と言いつつ、KnobsSvelte 未対応でした対応してました!

結びの言葉

Svelteにはスタイルガイドやベストプラクティスがまだありませんが、ReactVueの設計手法を取り入れることが出来ます。もしSvelteで開発していて解決策が見つからないときは、ReactVueでもググってみてください。きっと良い解決方法が見つかると思います。

また、私の作った「クリスマスツリーを飾ろう」を是非、触ってみてください!!
ちなみに、娘には3分で飽きられ、妻には「音は出ないの?ダダダダッて飾れないの?こう〜(以下略)」と不満の声が絶えなかったゲームです。

昨日のアドベントカレンダーで弊社の爆速の貴公子@takaHAL「Svelte + Vercel 爆速でサイトを公開する」と題打って記事を執筆しました。「クリスマスツリーを飾ろう」Vercelにアップするきっかけにもなりました。ありがとさん!

「クリスマスツリーを飾ろう」
https://svelte-christmas-tree.vercel.app/

Githubコードはこちら
https://github.com/Makohan/svelte-christmas-tree

明日は@odn_chiくん、よろしく!

余談...

開発に着手して数時間後の様子がこちらです。

プレイ前 プレイ後

「...これ、クリスマスツリーの飾り付けっていうか、ダンジョンメーカーじゃないか?!」

もう、バカです。バカバカバカ。