“[vuex] 状态字段 foo 被 foobar 上的同名模块覆盖”,在玩笑中使用 deepmerge 辅助函数

Posted

技术标签:

【中文标题】“[vuex] 状态字段 foo 被 foobar 上的同名模块覆盖”,在玩笑中使用 deepmerge 辅助函数【英文标题】:"[vuex] state field foo was overridden by a module with the same name at foobar" with deepmerge helper function in jest 【发布时间】:2020-08-22 11:15:32 【问题描述】:

我正在使用辅助函数在我的笑话中创建一个商店。辅助函数使用 deepmerge 将基本配置与自定义配置合并。这会导致多个控制台警告

[vuex] state field "cart" was overridden by a module with the same name at "cart"
[vuex] state field "customer" was overridden by a module with the same name at "customer"
[vuex] state field "checkout" was overridden by a module with the same name at "checkout"

store.js(为了演示目的而减少到最低限度)

import cart from './modules/cart'
import checkout from './modules/checkout'
import customer from './modules/customer'

Vue.use(Vuex)

export const config = 
    modules: 
        cart,
        customer,
        checkout,
    ,


export default new Vuex.Store(config)

test-utils.js

import merge from 'deepmerge'
import  config as storeConfig  from './vuex/store'

// merge basic config with custom config
export const createStore = config => 
    const combinedConfig = (config)
        ? merge(storeConfig, config)
        : storeConfig
    return new Vuex.Store(combinedConfig)

利用里面的辅助函数

somejest.test.js

import  createStore  from 'test-utils'

const wrapper = mount(ShippingComponent, 
    store: createStore(
        modules: 
            checkout: 
                state: 
                    availableShippingMethods: 
                        flatrate: 
                            carrier_title: 'Flat Rate',
                        ,
                    ,
                ,
            ,
        ,
    ),
    localVue,
)

如何解决控制台警告?

【问题讨论】:

【参考方案1】:

我认为在这种情况下,警告有点误导。这在技术上是正确的,只是没有帮助。

以下代码将生成相同的警告。它不使用deepmergevue-test-utilsjest,但我相信根本原因与原始问题相同:

const config = 
  state: ,

  modules: 
    customer: 
  


const store1 = new Vuex.Store(config)
const store2 = new Vuex.Store(config)
<script src="https://unpkg.com/vue@2.6.11/dist/vue.js"></script>
<script src="https://unpkg.com/vuex@3.4.0/dist/vuex.js"></script>

触发警告需要此示例的两个关键部分:

    多家商店。 配置中的根 state 对象。

问题中的代码肯定有多个商店。一个在store.js末尾创建,另一个由createStore创建。

该问题未显示根 state 对象,但确实提到代码已减少。我假设完整的代码确实有这个对象。

那么为什么会触发这个警告呢?

模块state 存储在根state 对象中。即使我的示例中的模块没有明确包含任何state,它仍然存在。此state 将存储在state.customer。因此,当创建第一个商店时,它会向该根 state 对象添加一个 customer 属性。

目前没有问题。

但是,当创建第二个存储时,它使用相同的根 state 对象。在此阶段复制或合并配置将无济于事,因为复制的 state 也将具有 customer 属性。第二个商店也尝试将customer 添加到根state。但是,它发现该属性已经存在,会感到困惑并记录警告。

官方文档中对此有一些报道:

https://vuex.vuejs.org/guide/modules.html#module-reuse

解决此问题的最简单方法是改用根 state 的函数:

state: () => ( /* all the state you currently have */ ),

每个商店都会调用该函数并获取自己的状态副本。这与对组件使用data 函数相同。

如果您实际上不需要 root state,您也可以通过完全删除它来修复它。如果没有指定state,那么 Vuex 每次都会创建一个新的根 state 对象。

【讨论】:

【参考方案2】:

当状态中的属性名称与模块名称冲突时记录,如下所示:

new Vuex.Store(
  state: 
    foo: 'bar'
  ,
  modules: 
    foo: 
  
)

因此会引发警告。


new Vuex.Store((
  state: 
    cart: '',
    customer: '',
    checkout: ''
  ,
  modules: 
    cart: ,
    customer: ,
    checkout: ,
  
))


最有可能在这里

export const createStore = config => 
    const combinedConfig = (config)
        ? merge(storeConfig, config)
        : storeConfig
    return new Vuex.Store(combinedConfig)

来自 vuex 的源代码,它有助于指出这些错误在哪里引发以进行日志记录。

如果您在生产环境中运行应用程序,您知道不会引发此警告......或者您可能会拦截警告并立即返回;

vuex source code

const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => 
  if (__DEV__) 
    if (moduleName in parentState) 
      console.warn(
        `[vuex] state field "$moduleName" was overridden by a module with the same name at "$path.join('.')"`
      )
    
  
  Vue.set(parentState, moduleName, module.state)
)

vuex tests

jest.spyOn(console, 'warn').mockImplementation()
const store = new Vuex.Store(
  modules: 
    foo: 
      state () 
        return  value: 1 
      ,
      modules: 
        value: 
          state: () => 2
        
      
    
  
)
expect(store.state.foo.value).toBe(2)
expect(console.warn).toHaveBeenCalledWith(
  `[vuex] state field "value" was overridden by a module with the same name at "foo.value"`
)

【讨论】:

【参考方案3】:

好吧,我相信没有必要在 test-utils.ts 中使用deepmerge。我们最好使用 Vuex 本身来处理模块的合并,而不是与其他方法合并。

如果您在模拟模块上看到 Vuex testing with Jest 的文档

您需要传递所需的模块。


import  createStore, createLocalVue  from 'test-utils';
import Vuex from 'vuex';

const localVue = createLocalVue()

localVue.use(Vuex);

// mock the store in beforeEach

describe('MyComponent.vue', () => 
  let actions
  let state
  let store

  beforeEach(() => 
    state = 
      availableShippingMethods: 
        flatrate: 
          carrier_title: 'Flat Rate',
        ,
      ,
    

    actions = 
      moduleActionClick: jest.fn()
    

    store = new Vuex.Store(
      modules: 
        checkout: 
          state,
          actions,
          getters: myModule.getters // you can get your getters from store. No need to mock those 
        
      
    )
  )
);

吹口哨,在测试用例中:

const wrapper = shallowMount(MyComponent,  store, localVue )

希望这会有所帮助!

【讨论】:

以上是关于“[vuex] 状态字段 foo 被 foobar 上的同名模块覆盖”,在玩笑中使用 deepmerge 辅助函数的主要内容,如果未能解决你的问题,请参考以下文章