Нюансы использования ООП, Ext.Config в ExtJS

С самого начала ExtJS реализовала систему классов для расширения JavaScript. Цель системы классов заключалась в поддержке эффективной организации кода и реализации псевдоклассического наследования, существующего во многих объектно-ориентированных языках программирования. Давным давно, с выходом ExtJS 4.0 Sencha решила пересмотреть свою систему классов и улучшить ее с помощью большего количества объектно-ориентированных методов. В частности, они разработали конфигурационную систему классов, наверное хоть раз вы видели свойство config: { ... } в определении классов в ExtJS. Эта статья посвящена обзору возможностей проектирования классов.

Определение классов

В ExtJS большинство классов наследуется от Ext.Base, за некоторым исключением классы должны следовать определенному соглашению об именах, связанному непосредственно с их файловым путем. Вот пример определения класса, создания экземпляра и его использования. Fiddle доступен тут.

// Define class with name "Car"
Ext.define('Car', {
    // Define config properties
    config : {
        make  : undefined,
        model : undefined
    },
    // First method that gets called upon class instantiation
    constructor : function (config) {
        // Initializes config properties
        this.initConfig(config);
    },
    // Define drive method
    drive : function () {
        console.log('Driving ' + this.getMake() + ' ' + this.getModel());
    }
});

// Instantiate class "Car" with config properties
var car = Ext.create('Car', {
    make  : 'Honda',
    model : 'Accord'
});

// Call method "drive" on class instance
// Outputs "Driving Honda Accord" to the console
car.drive();

Использование свойств конфигурации

Когда вы объявляете какое либо свойство с помощью директивы  config: { ... } в определении класса, то движок ExtJS сгенерирует 2 основных метода для работы с этим свойством конфигурации.

1. Getter (создается автоматически)

Стандартный метод доступа, который возвращает текущее значение свойства конфигурации. Пример Fiddle тут.

car.getMake(); // returns “Honda”

2. Setter (создается автоматически)

Мутатор, который устанавливает значение свойства конфигурации в экземпляре класса. Пример Fiddle тут.

car.setMake(“Toyota”);
car.getMake(); // returns “Toyota”

3. Applier (создается вручную)

Эта функция вызывается setter’ом при изменении свойства конфигурации (например, из undefined до Foo). Данная функция должна вернуться значение, которое будет установлено в данное свойство конфигурации. По сути это prehook на установку значения. Обычно данный метод определяется для выполнения какой-либо логики перед установкой значения конфигурации. Пример Fiddle тут.

// Example applier definition
applyMake : function (newValue, oldValue) {
    // Execute some arbitrary logic
    if(oldValue.length > newValue.length) {
        return oldValue.toUpperCase();
    } 
 
    return newValue.toUpperCase(); 
}

4. Updater (создается вручную)

Эта функция вызывается setter’ом после изменении свойства конфигурации (например, из Foo до Bar). По сути это posthook на установку значения. Обычно используется классом для реакции на изменение свойства конфигурации. Пример Fiddle тут.

// Example updater definition
updateModel : function (newValue, oldValue) {
    if(newValue === 'Accord') {
        this.setMake('Honda');
    } else if(newValue === 'Camry') {
        this.setMake('Toyota');
    }
}

Типовые ситуации использования

Изменение состояние другого компонента, при изменении значения свойства конфигурации.

// Enable Drive button if make is set
applyMake : function (newValue, oldValue) {
    if(newValue) {
      this.down('button#drivebutton').enable();
    }
    return newValue;
}

Создание требуемой зависимость после установки значения свойства конфигурации.

// Instantiate a store if "storeUrl" config property is set
applyStoreUrl : function (url) {
    if(url) {
        this.setStore(Ext.create('Ext.data.Store', {
            fields : ['name'],
            proxy  : {
                type : 'ajax',
                url  : url,
            }
        }));
    }
       
    return url;
}

Создание дочернего класса на основе значения конфигурации.

// Instantiate Ext.Toolbar with config and return class instance
applyHeaderToolbar : function (cfg) {
    if(cfg) {
        var tbar = this.tbar = Ext.create('Ext.Toolbar', cfg);
        return tbar;
    }
}

Теперь мы разобрались с классовой системой которая появилась в ExtJS 4.0 . Далее я расскажу о новых функциях Ext.Config, которые появились в ExtJS 6.

Ext.Config

1. cached

Если cached установлено как true, свойство config будет храниться в прототипе класса, как только у первого экземпляра была возможность обработать значение по умолчанию. То есть другими словами Applier будет вызван с дефолтным значением, только для первого экземпляра класса — если значение явно не указанно. Пример можно посмотреть тут , а если лень то GIF чуть ниже.

config: {
    foo: {
        $value: 42,
        cached: true
    }
}

Cached — очень специфичное свойство, реализация может быть найдена в свойстве Ext.util.ElementContainer.childEls. Кэширование childEls в прототипе является оптимизацией — оно сохраняет память и снижает нагрузку на CPU при создании множества инстансов класса.

childEls: {
    $value: {},
    cached: true,
    lazy: true,
 
    merge: function (newValue, oldValue, target, mixinClass) {

Проверка свойств конфигураций на директиву cached легко достигается путем доступа к свойству cachedConfigs с помощью метода getConfigurator() :

myObj.self.getConfigurator().cachedConfigs

Это становится возможным из-за того что Ext.Configurator, создается при первом вызове getConfigurator() для определенного класса.

2. evented

Если evented установлено как true, свойство конфигурации будет рассматриваться как Ext.Evented. Это означает, что всякий раз, когда вызывается setter этого свойства конфигурации, ExtJS автоматически создает событие [configname + change]. Обратите внимание, что вы должны подключить миксин Ext.mixin.Observable и правильно его проинициализировать в конструкторе. Пример использования на Fiddle тут, а GIF чуть ниже.

Ext.define('MyClass', {
    mixins: ['Ext.mixin.Observable'],
    config: {
        foo: {
            $value: 42,
            evented: true
        }
    },
    constructor: function(config) {
        console.log('MyClass Instantiated');
        this.initConfig(config);
        this.mixins.observable.constructor.call(this, config);
        return this;
    }
});
myObj = new MyClass();
myObj.on('foochange', function() {
   console.log(arguments);
});

Другим способом определения evented свойств конфигураций является использование eventedConfig класса Ext.Evented, который обрабатывается в процессе работы onClassExtended:

Ext.define('MyEventedClass', {
    extend: 'Ext.Evented',
 
    eventedConfig: {
        boo: 34
    },
    constructor: function(config) {
        this.initConfig(config);
        this.mixins.observable.constructor.call(this, config);
        return this;
    }
});
myEventedObj = new MyEventedClass();
myEventedObj.on('boochange', function() {
    console.log('boochange');
    console.log(arguments);
});

Определенный класс должен наследоваться от Ext.Evented для создания evented свойств конфигураций. Реализация этого подхода может быть найдена в свойстве Ext.Widget.width.

3. lazy

Если lazy установлено ​​как true, свойство конфигурации не будет немедленно инициализировано во время вызова initConfig().

config: {
     foo: {
         $value: 42,
         lazy: true
     }
 }

Пример на Fiddle можно найти тут, GIF ниже.

Реализация может быть найдена в свойстве Ext.app.Application.mainView, где mainView создается именно как lazy. Таким образом, с точки зрения производительности, он создается, когда он используется, а не когда приложение создается.

4. merge

Конфигурация merge принимает функцию, которая будет вызвана при создании экземпляров или если уже определены производные классы. Функция merge принимает новые значения и унаследованное значение и возвращает комбинированное значение конфигурации. При последующих вызовах это возвращаемое значение будет предоставлено как oldValue через аргумент. Пример на Fiddle тут, картинка ниже.

Ext.define('MyClass', {
    config: {
        foo: {
            $value: [42],
            merge: function(newValue, oldValue) {
                var val = [].concat(newValue, oldValue);
                return val;
            }
        }
    },
    constructor: function(config) {
        this.initConfig(config);
        return this;
    }
});
 
Ext.define('MyExtendedClass', {
    extend: 'MyClass',
    foo: [23]
});
 
var myObj = new MyClass({
    foo: 123
});
//MyClass.foo:  – [123, 42]
 
var myExtendedObj = new MyExtendedClass({
    foo: 321
});
//MyExtendedClass.foo:  – [321, 23, 42]

Реализация может быть найдена в свойстве Ext.panel.Header.title, где merge используется для создания заголовка по умолчанию:

title: {
    $value: {
        xtype: 'title',
        flex: 1
    },
    merge: function(newValue, oldValue) {
        if (typeof newValue !== 'object') {
            newValue = {
                text: newValue
            };
        }
        return Ext.merge(oldValue ? Ext.Object.chain(oldValue) : {}, newValue);
    }
}

Кроме того, вы можете использовать метод Ext.Config.mergeSets для выполнения ваших слияний:

twoWayBindable: {
    $value: null,
    lazy: true,
    merge: function (newValue, oldValue) {
        return this.mergeSets(newValue, oldValue);
    }
}

 

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *