如何使Vuex商店与Storybook配合使用?

时间:2019-06-20 09:00:09

标签: vue.js vuex storybook

我有一个组件的故事,要求我的Vuex商店中的ACTION执行API调用。但是,无法通过Storybook找到商店:Unhandled promise rejection TypeError: "this.$store is undefined"

我试图通过createdmounted Vue生命周期挂钩访问商店,但是每个挂钩都返回了undefined

我的Vuex商店在我的应用程序中正常工作。

我使用故事书5.0.1和vuex 3.1.1

这是我的故事书config.js

// Taken from https://davidwalsh.name/storybook-nuxt & https://github.com/derekshull/nuxt-starter-kit-v2/blob/master/.storybook/config.js
import { addParameters, configure } from '@storybook/vue';
import { withOptions } from '@storybook/addon-options';
import { setConsoleOptions } from '@storybook/addon-console';
import { create } from '@storybook/theming';
import Vue from 'vue';
import VueI18n from 'vue-i18n';

// Vue plugins
Vue.use(VueI18n);

setConsoleOptions({
  panelExclude: [],
});

// Option defaults:
addParameters({
  options: {
    /**
     * show story component as full screen
     * @type {Boolean}
     */
    isFullScreen: false,
    /**
     * display panel that shows a list of stories
     * @type {Boolean}
     */
    showNav: true,
    /**
     * display panel that shows addon configurations
     * @type {Boolean}
     */
    showPanel: true,
    /**
     * where to show the addon panel
     * @type {String}
     */
    panelPosition: 'bottom',
    /**
     * sorts stories
     * @type {Boolean}
     */
    sortStoriesByKind: false,
    /**
     * regex for finding the hierarchy separator
     * @example:
     *   null - turn off hierarchy
     *   /\// - split by `/`
     *   /\./ - split by `.`
     *   /\/|\./ - split by `/` or `.`
     * @type {Regex}
     */
    hierarchySeparator: /\/|\./,
    /**
     * regex for finding the hierarchy root separator
     * @example:
     *   null - turn off multiple hierarchy roots
     *   /\|/ - split by `|`
     * @type {Regex}
     */
    hierarchyRootSeparator: /\|/,
    /**
     * sidebar tree animations
     * @type {Boolean}
     */
    sidebarAnimations: true,
    /**
     * enable/disable shortcuts
     * @type {Boolean}
     */
    enableShortcuts: true,
    /**
     * theme storybook, see link below
     */
    theme: create({
      base: 'light',
      brandTitle: '',
      brandUrl: '',
      // To control appearance:
      // brandImage: 'http://url.of/some.svg',
    }),
  },
});

const req = require.context('../src/components', true, /\.story\.js$/)

function loadStories() {
  req.keys().forEach((filename) => req(filename))
}

configure(loadStories, module);

这是我组件的故事:

import { storiesOf } from '@storybook/vue';
import { withReadme } from 'storybook-readme';
import { withKnobs } from '@storybook/addon-knobs';
import HandoffMainView from './HandoffMainView.vue';
import readme from './README.md';

storiesOf('HandoffMainView', module)
  .addDecorator(withReadme([readme]))
  .addDecorator(withKnobs)
  .add('Default', () => {
    /* eslint-disable */
    return {
      components: { HandoffMainView },
      data() {
        return {
          isLoading: true,
          component: {
            src: '',
            data: [],
          },
        };
      },
      template: '<handoff-main-view :component="component" />',
    };
  });

这是我的组成部分:

<template>
  <main class="o-handoff-main-view">
    <div class="o-handoff-main-view__content">
      <div
        :class="[
          'o-handoff-main-view__background',
          background ? `o-handoff-main-view__background--${background}` : false
        ]"
      >  
        <loader
          v-if="isLoading"
          :color='`black`'
          class="o-handoff-main-view__loader"
        />
        <div
          v-else
          class="o-handoff-main-view__ui-component"
          :style="getUiComponentStyle"
        >
          <img
            :src="uiComponent.src"
            alt=""
          >
          <handoff-main-view-layer-list
            :layers="uiComponent.data"
          />
        </div>
      </div>
    </div>
    <div class="o-handoff-main-view__controls">
      <handoff-main-view-zoom-handler
        :default-zoom-level="zoomLevel"
        :on-change="updateZoomLevel"
      />
    </div>
  </main>
</template>

<script>
  import { mapActions } from 'vuex';
  import Loader from '../../01-atoms/Loader/Loader.vue';
  import HandoffMainViewZoomHandler from '../HandoffMainViewZoomHandler/HandoffMainViewZoomHandler.vue';
  import HandoffMainViewLayerList from '../HandoffMainViewLayerList/HandoffMainViewLayerList.vue';

  export default {
    components: {
      Loader,
      HandoffMainViewZoomHandler,
      HandoffMainViewLayerList,
    },
    props: {
      background: {
        type: String,
        default: 'damier',
      },
      component: {
        type: Object,
        required: true,
      },
    },
    data() {
      return {
        isLoading: true,
        zoomLevel: 1,
        uiComponent: {
          src: null,
        }
      };
    },
    mounted() {
      this.setUiComponentImage();
    },
    methods: {
      ...mapActions('UiComponent', [
        'ACTION_LOAD_SIGNED_URLS'
      ]),
      async setUiComponentImage() {
        const uiComponentImg = new Image();
        const signedUrls = await this.ACTION_LOAD_SIGNED_URLS([this.component.id]);
        uiComponentImg.onload = () => {
          this.isLoading = false;
        };
        uiComponentImg.src = this.uiComponent.src;
      },
    },
  };
</script>

3 个答案:

答案 0 :(得分:1)

在故事中传递新的商店实例(或嘲笑)

import Vuex from "vuex";
import { storiesOf } from '@storybook/vue';
import { withReadme } from 'storybook-readme';
import { withKnobs } from '@storybook/addon-knobs';
import HandoffMainView from './HandoffMainView.vue';
import readme from './README.md';

storiesOf('HandoffMainView', module)
  .addDecorator(withReadme([readme]))
  .addDecorator(withKnobs)
  .add('Default', () => {
    /* eslint-disable */
    return {
      components: { HandoffMainView },
      data() {
        return {
          isLoading: true,
          component: {
            src: '',
            data: [],
          },
        };
      },
      template: '<handoff-main-view :component="component" />',
      store: new Vuex.Store({ // here
        modules: {
          namespaced: true,
          actions: ... 
        }
      }
    };
  });

答案 1 :(得分:0)

我打赌您的应用中的某个地方(可能是main.js),您正在执行以下操作:

import Vuex from 'vuex';
Vue.use(Vuex);

const store = new Vuex.Store({
  state,
  mutations,
  getters,
});

然后,在创建Vue应用程序时,您会呼叫new Vue({store, i18n...})

您已经在config.js中使用'i18n'模块来伪造Vue。您还需要导入Vuex和那里的商店。


现在,在您的故事书设置中必须导入商店(或模拟商店)可能是您的组件过大或与商店耦合过多的味道。

通常,故事书更适合于显示那些显示具有专用功能的内容(表单控件,事物列表...)的组件。这些组件通常通过道具和事件与应用程序的其余部分通信。我们称其为演示组件。

相反,与商店通信的组件通常是视图或页面,它们协调状态并与后端通信,并向后端提供数据。

我认为您应该在故事书上仅展示演示性组件,并避免谈论其中的全局模块。至少,我相信这是故事书背后的精神以及主要用途。这可能是因为您没有在故事书中找到太多关于如何模拟商店的文档:我认为,故事书项目通常通常不会首先连接到vuex。

答案 2 :(得分:0)

如果您使用的是Nuxt.js,请按以下步骤操作:

./ storybook / store.js

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

const store = new Vuex.Store({
    state: require("../store/index.js").state,
    getters: require("../store/index.js").getters,
    actions: require("../store/index.js").actions,
    mutations: require("../store/index.js").mutations,

    modules: {
        ads: {
            namespaced: true,
            state: require("../store/ads.js").state,
            getters: require("../store/ads.js").getters,
            actions: require("../store/ads.js").actions,
            mutations: require("../store/ads.js").mutations
        },

        features: {
            namespaced: true,
            state: require("../store/features.js").state,
            getters: require("../store/features.js").getters,
            actions: require("../store/features.js").actions,
            mutations: require("../store/features.js").mutations
        },


        user: {
            namespaced: true,
            state: require("../store/user.js").state,
            getters: require("../store/user.js").getters,
            actions: require("../store/user.js").actions,
            mutations: require("../store/user.js").mutations
        },
    }
});

export default store

然后在您的故事中:

// ...
import store from '@/.storybook/store';

export default {
    title: 'MyComponent'
};

export const MyComponentStory = () => ({
    store: store,
    // ...
})