🍣

Vue + Storybook環境でemitのロギングを簡単にしたい

2023/09/27に公開

はじめに

UI コンポーネントが発火したイベント (emit) をストーリー上でロギングするのに役立つ Actions Addon (@storybook/addon-actions) という公式アドオンがあります。

React では parameters.actions.argTypesRegex というプレビュー設定で簡単にロギングできるのですが、Vue は少々工夫が必要です。

今回はストーリーファイルをシンプルに保ちつつ、Vue コンポーネントをデバッグしやすくできる方法を見つけたのでご紹介します。

執筆時点のバージョン

  • Storybook 7.4.5
  • TypeScript 5.2.2
  • Vue 3.3.4

前提

設定方法は shingo.sasaki さんの本(無料)をご覧ください。とても参考になります。
https://zenn.dev/sa2knight/books/storybook-7-with-vue-3

コンポーネントの準備

イベントが発生するコンポーネントなら何でも OK です。

src/components/SampleButton.vue
<script setup lang="ts">
/**
 * Vue 3.3+ の構文は未対応なので注意
 * @see https://ja.vuejs.org/api/sfc-script-setup.html#defineprops-defineemits
 */
const emit = defineEmits<{
  /** クリックしたときに発火する */
  (evt: "click", event: Event): void;
}>();
</script>

<template>
  <button @click="emit('click', $event)">
    Click me!
  </button>
</template>

ストーリーの準備

autodocs が有効かつ対象コンポーネントに v-bind="args" しているストーリーであれば何でも OK です。

src/components/SampleButton.stories.ts
import { type Meta, type StoryObj } from '@storybook/vue3';

import SampleButton from './SampleButton.vue';

type Story = StoryObj<typeof SampleButton>;

const meta: Meta<typeof SampleButton> = {
  component: SampleButton,

  parameters: {
    controls: {
      expanded: true,
    },
  },

  tags: [
    'autodocs',
  ],

  render: (args) => {
    return {
      components: {
        SampleButton,
      },

      setup: () => ({
        args,
      }),

      template: `
        <SampleButton
          v-bind="args"
        />
      `,
    };
  },
};

export default meta;

export const Default: Story = {};

Storybook の設定

ArgTypesEnhancer を作ってプレビュー設定に加えます。後で解説しますので、とにかくコピー & ペーストしてみてください。

.storybook/preview-utils/argTypesEnhancers.ts
import { toHandlerKey } from 'vue';
import { type ArgTypesEnhancer, type StrictInputType } from '@storybook/types';

export const addActionsWithEmits: ArgTypesEnhancer = ({ argTypes }) => {
  const argTypesEntries = Object.entries(argTypes)
    .filter(([, argType]) => argType.table?.category === 'events')
    .map(([name]) => {
      /**
       * 例:`click` という events に対して `onClick` という名称の argType + action を追加することで、v-on によるイベントのバインディングが可能となる
       * @see https://ja.vuejs.org/guide/extras/render-function.html#v-on
       */
      const newName = toHandlerKey(name);
      const newArgType: StrictInputType = {
        name: newName,
        action: name,
        table: {
          disable: true, // Controls には表示しない
        },
      };

      return [newName, newArgType] as const;
    });

  return {
    ...argTypes,
    ...Object.fromEntries(argTypesEntries),
  };
};
.storybook/preview.ts
import { type Preview } from '@storybook/vue3';

import { addActionsWithEmits } from './preview-utils/argTypesEnhancers';

const preview: Preview = {
  argTypesEnhancers: [
    addActionsWithEmits,
  ],
};

export default preview;

ブラウザで触ってみる

まずは Default ストーリーを表示しましょう。autodocs された click イベントの説明を閲覧できます。

Actions タブに切り替え、SampleButton コンポーネントを何度かクリックしてみてください。
コンポーネントが発火したイベントがロギングされるようになります。

解説

公式ドキュメントにはストーリーの argTypes で action: true または action: 'event name' を設定すると、ロギング用のハンドラーを args へ自動設定してくれる仕組みが記載されています。
https://storybook.js.org/docs/vue/essentials/actions#action-argtype-annotation

該当する Actions Addon のソースコードはこちら。
https://github.com/storybookjs/storybook/blob/a3cdabb025524822807318bc137f69be006596c2/code/addons/actions/src/addArgsHelpers.ts#L43-L63

今回作成した関数 addActionsWithEmits はプレビュー設定 parameters.actions.argTypesRegex と似た仕組みで action: 'event name' を自動的に設定し、Actions Addon の対象とさせるロジックなのでした。

もっと解説

今回のサンプルストーリーは argTypes を何も設定していないのですが、実は autodocs 機能によって自動的に設定されています。
具体的には以下の構造を持つオブジェクトが設定されています。

{
  argTypes: {
    click: {
      description: 'クリックしたときに発火する',
      name: 'click',
      table: {
        category: 'events',
      },
    },
  },
}

addActionsWithEmits 関数は table.category === 'events' の条件を持つ argType を抜き出し、Actions Addon 用の argType を新たに追加しています。

実行後は以下の構造になります。lowerCamelCase の onClick というキーかつ action を設定することが重要です。

{
  argTypes: {
    click: {
      /* ... */
    },

    onClick: {
      name: 'onClick',
      action: 'click',
      table: {
        disable: true,
      },
    },
  },
}

こうすることで、Actions Addon によってロギング用の onClick がストーリーの args へ自動的に追加されます。

{
  argTypes: {
    /* ... */
  },

  args: {
    onClick: action('click'),
  },
}

さて、Vue 3 には onClick という形式のキーで v-bind すると、@click と同等の効果を得られる隠し機能みたいなものがあります。まるで React みたいですね。
https://ja.vuejs.org/guide/extras/render-function.html#v-on

Actions Addon の機能と Vue の仕様をかけ合わせることで、最終的にはストーリーテンプレートを以下のように記述したのと同様の状態になります。

<SampleButton v-bind="{ onClick: action('click') }" />

おわりに

Vue のイベント名と Actions Addon の仕様が微妙に噛み合わないので、私はこれまで煩雑なストーリーファイルを作りがちでしたが、この方法を見つけてから Storybook のメンテナンスコストを大幅に抑えられるようになりました。

一回設定してしまえば Storybook によるデバッグが便利になりますので、ぜひ参考にして頂ければと思います。

Discussion