😎

リファクタ目的のVue + Storybook で Injectが便利

2022/03/13に公開

前回 Vue + Storybook + reg-suit で VisualRegressionTestのフレームワークを構築しました。
https://zenn.dev/kozayupapa/articles/2ddf2bb062acc7

そのstoryを拡充していきたいと思います。

何がしたい?

前提としては、今回StorybookのStoryを追加したいのは、 リファクタリングが必要なくらいvueのcomponent,ページが巨大になってしまっているケースです。すべての変数がpage のdataとして定義されているような例です。

prop になっていれば、storyからのテストデータ設定も楽なのですが、
dataのなかみをtest story側から好きな値に書き換える方法がないかを検討していました。

Vue Provide/Injectとは?

地道にvueのドキュメントを読み進んでいたら、良さそうなものがありました。
離れた階層からでも提供でき、 どこから挿入されたかは指揮せずに Inject されたあとのコードを受け側に追加すれば良い。ほしかったのはこれです!

https://v3.ja.vuejs.org/guide/component-provide-inject.html

image.png

provide/inject例
// 送り側
app.component('todo-list', {
  data() {
    return {
      todos: ['Feed a cat', 'Buy tickets']
    }
  },
  provide() {
    return {
      todoLength: this.todos.length
    }
  },
  template: `
    ...
  `
})

//受け側
app.component('todo-list-statistics', {
  inject: ['todoLength'],
  created() {
    console.log(`Injected property: ${this.todoLength}`) // > Injected property: 5
  }
})

ただし、 テスト時だけデータを受け渡したいので、通常時はいままで通りの初期値が設定されていてほしいです。
上記のページだけでは初期値の与え方がわからなかったので、さらに読みすすめると以下のAPIドキュメントに詳しくのっていました。

https://v3.ja.vuejs.org/api/options-composition.html#provide-inject

初期値の与え方例
const Child = {
  inject: {
    foo: { default: 'foo' }
  }
}

すてき、なんて便利なの。
さらにInjectされた値をdata の初期値として渡す例も丁寧に書いてくれています。

const Child = {
  inject: ['foo'],
  data() {
    return {
      bar: this.foo
    }
  }
}

しかし!! 
通常のvueでよく見かける単一ファイルコンポーネントのなかの書き方で
上記の書き方をするとundefinedになってしまい失敗します。

NG 例
  export default {
    components: {
      hoge
    },
    data: () => ({
      foo:  this.fooInject,
    }),
    inject: { 
      fooInject :{default : false},
    },
    computed: {
     ...

こちらも 最初できないのかと思っていましたが Documentをよく読んでみると
下記に解説がありました。 なるほどなるほど componentのインスタンスを指してなかったんですね。

https://v3.ja.vuejs.org/api/options-data.html#data-2

data プロパティでアロー関数を使う場合、this はコンポーネントのインスタンスになりませんが、
関数の第 1 引数としてそのインスタンスにアクセスできるということは忘れないでください。

data: vm => ({ a: vm.myProp })

上記の例を下記のように変更することで無事data プロパティの初期値に設定することも動作しました

OK例
  export default {
    components: {
      hoge
    },
    data: (vm) => ({
      foo:  vm.fooInject,
    }),
    inject: { 
      fooInject :{default : false},
    },
    computed: {
     ...

storybook storyの書き方

テスト対象(Injectされる側)の必要な変更は上記のように最小にしておきます。
テストコード(今回はstorybookのstory) の書き方は下記のようになります。

SomePage.stories.js
import idea from '../../src/views/SomePage';

// More on default export: https://storybook.js.org/docs/vue/writing-stories/introduction#default-export
export default {
  title: 'somepage',
  component: some,
  excludeStories: /.*Data$/,
 };


/* story記述 */
// default
export const Default = () => ({    // 変数名がナビゲーションパネルでの表示名となる
    components: { some },  // 対象となるコンポーネントを指定する
    template: '<some />'  // レンダリングするhtmlを記述する
  });

// check foo
export const checkFoo = () => ({    // 変数名がナビゲーションパネルでの表示名となる
  components: { some },  // 対象となるコンポーネントを指定する
  provide(){
    return {fooInject:true}
  },
  template: '<some />'  // レンダリングするhtmlを記述する
});

// check baa
export const checkBaa = () => ({    // 変数名がナビゲーションパネルでの表示名となる
  components: { some },  // 対象となるコンポーネントを指定する
  provide(){
    return {
      baaInject:true,
      contentInject:'test baa from storybook scenario',
    }
  },
  template: '<some />'  // レンダリングするhtmlを記述する
});

これで vueの data プロパティをすきに変えて 全画面遷移のVRTがかきやすくなります!

Discussion