当需要父视图模型时,Extjs 使用子视图模型数据

Extjs uses child-viewmodel data when parent-viewmodel is desired

考虑这样的片段(可以是 运行 在 https://fiddle.sencha.com/,并在右上角的组合框中选择经典而不是现代):

Ext.define('ReusableComponent', {
    xtype: 'reusable',
    extend: 'Ext.Container',
    
    config: {
        foo: 'Foo'
    },
    
    updateFoo: function(foo) {
        this.getViewModel().set('foo', foo);  
    },
    
    viewModel: {
        data: {
            foo: 'Foo'
        }
    },
    
    items: [{
        bind: {
            html: 'foo = {foo}'
        }
    }]
});

Ext.define('ConcreteComponent', {
    extend: 'Ext.panel.Panel',
    
    viewModel: {
        data: {
            foo: 'Bar'
        }
    },
    
    layout: 'fit',
    items: [{
        xtype: 'reusable',
        bind: {
            foo: '{foo}'
        }
    }]
});

Ext.application({
    name : 'Fiddle',

    launch : function() {
        Ext.create('ConcreteComponent', {
            renderTo: Ext.getBody(),
            title: 'ConcreteComponent',
            width: 200,
            height: 200
        });
    }
});

梦想是有一个可重用的组件,它有一个定义的外部接口,知道它就足够了。使用该组件的人不必了解其内部结构。在这个例子中,外部接口是普通配置,它继承自 Ext.Container(如 width/height/etc)和 foo config.

所以说我然后尝试在 ConcreteComponent 中使用它。我知道可重用组件具有配置 foo,因此我应该能够将它绑定到我自己的视图模型,这就是我所做的。然而,这不起作用,它显示 foo = Foo,而不是(预期的)foo = Bar。原因似乎很清楚——我 不知不觉地 使用了子视图模型中已经存在的名称,而 extjs 选择了它而不是我在 ConcreteComponent 中定义的名称。如何解决这个问题也很清楚(例如,在 ConcreteComponent 中,将视图模型数据 属性 从 foo 重命名为 foo2)。 但这迫使我们了解该可重用组件的内部结构,而不仅仅是它的 public 接口 。无论如何要解决这个问题?或者无论如何都应该将子视图模型始终视为其 public 界面的一部分?

看起来解决方案是简单地在私有 属性 下手动创建视图模型(这打破了视图模型的子父链 extjs 在组件的视图模型及其容器的视图模型 https://docs.sencha.com/extjs/6.2.0/classic/Ext.Component.html#cfg-viewModel), and pass it to reusable component's children explicitly via viewModel config. Using defaults seems to work fine. I saw the solution when stumbling upon color picker's source code https://docs.sencha.com/extjs/6.2.0/classic/src/Selector.js.html 之间创建。这里是问题的固定码

Ext.define('ReusableComponent', {
    xtype: 'reusable',
    extend: 'Ext.Container',
    
    config: {
        foo: 'Foo'
    },
    
    updateFoo: function(foo) {
        this.childViewModel.set('foo', foo);  
    },
    
    constructor: function() {
        this.childViewModel = Ext.create('Ext.app.ViewModel', {
            data: {
                foo: 'Foo'
            }
        });
        this.defaults = {
            viewModel: this.childViewModel
        };
        this.callParent(arguments);
    },
    
    items: [{
        bind: {
            html: 'foo = {foo}'
        }
    }]
});

Ext.define('ConcreteComponent', {
    extend: 'Ext.panel.Panel',
    
    viewModel: {
        data: {
            foo: 'Bar'
        }
    },
    
    layout: 'fit',
    items: [{
        xtype: 'reusable',
        bind: {
            foo: '{foo}'
        }
    }]
});

Ext.application({
    name : 'Fiddle',

    launch : function() {
        Ext.create('ConcreteComponent', {
            renderTo: Ext.getBody(),
            title: 'ConcreteComponent',
            width: 200,
            height: 200
        });
    }
});