Pesquisa de site

javascript - Estendendo Array Incorporado com Classes ES6 - Erro 'Método não é uma função'


Estou tentando estender a classe de array incorporada e criar coleções personalizadas baseadas na classe de array nativa.

Consigo criar uma coleção com nome personalizado, funciona bem, mas não consigo usar meus métodos personalizados. Se eu usar o método findAnimal, ele me dá o erro method is not a function. Se eu registrar a coleção e inspecionar os protótipos, não consigo ver meus métodos personalizados.

Percebi que eu transpilo o código para o es5 e, se eu não fizer isso, funciona bem. O problema ocorre com o código reduzido para es5 pelo compilador ts.

Se usarmos o babel para rebaixar o código para es5, o código transpilado funciona bem. Ou o compilador ts está perdendo algo ao transpilar o código ou eu estou perdendo alguma configuração vital.

interface Animal {
    name: string;
    weight: number
}

class AnimalCollection extends Array <Animal> {
    constructor(name, ...items) {
        super(...items);

        Object.defineProperty(this, 'name', {
            enumerable: false,
            writable: false,
            value: name
        })
    }

    findAnimal(name): Animal {
        return this.find(a => a.name === name) || null;
    }
}

const animalsArray = [
    {name: 'TD-23', weight: 60},
    {name: 'TD-25', weight: 50},
    {name: 'TXD-26', weight: 120},
    {name: 'TYD-26', weight: 40}
];

const animals = new AnimalCollection('Deers', ...animalsArray)

console.log(animals.findAnimal('TD-23'));
// Uncaught TypeError: animals.findAnimal is not a function

ES5 Rebaixado

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __spreadArrays = (this && this.__spreadArrays) || function () {
    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
    for (var r = Array(s), k = 0, i = 0; i < il; i++)
        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
            r[k] = a[j];
    return r;
};

var AnimalCollection = /** @class */ (function (_super) {
    __extends(AnimalCollection, _super);
    function AnimalCollection(name) {
        var items = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            items[_i - 1] = arguments[_i];
        }
        var _this = _super.apply(this, items) || this;
        Object.defineProperty(_this, 'name', {
            enumerable: false,
            writable: false,
            value: name
        });
        return _this;
    }
    AnimalCollection.prototype.findAnimal = function (name) {
        return this.find(function (a) { return a.name === name; }) || null;
    };
    return AnimalCollection;
}(Array));
var animalsArray = [
    { name: 'TD-23', weight: 60 },
    { name: 'TD-25', weight: 50 },
    { name: 'TXD-26', weight: 120 },
    { name: 'TYD-26', weight: 40 }
];
var animals = new (AnimalCollection.bind.apply(AnimalCollection, __spreadArrays([void 0, 'Deers'], animalsArray)))();
console.log(animals.findAnimal('TD-23'));

Resposta #1

RESPOSTA ATUALIZADA

Para responder à sua pergunta modificada, o TypeScript não permite que você estenda tipos embutidos como Array, Error, etc. A razão está escrita aqui.

No ES2015, construtores que retornam um objeto substituem implicitamente o valor de this para quaisquer chamadas de super(...). É necessário que o código do construtor gerado capture qualquer valor de retorno potencial de super(...) e o substitua por this.

Como resultado, a criação de subclasses de Error, Array e outros pode não funcionar mais como esperado. Isso se deve ao fato de que as funções construtoras de Error, Array e similares usam new.target do ECMAScript 6 para ajustar a cadeia de protótipos; no entanto, não há como garantir um valor para new.target ao invocar um construtor no ECMAScript 5. Outros compiladores de versões anteriores geralmente têm a mesma limitação por padrão.

Portanto, se você precisar estender o Array embutido em um ambiente ES5, então você pode tentar o Babel para compilar seu código. No entanto, esteja ciente de que ele tem suas limitações conforme declarado aqui.

Classes integradas como Date, Array, DOM etc. não podem ser devidamente subclasseadas devido a limitações no ES5 (para o plugin transform-classes). Você pode tentar usar babel-plugin-transform-builtin-extend baseado em Object.setPrototypeOf e Reflect.construct, mas ele também tem algumas limitações.

RESPOSTA ANTIGA

Embora o próprio código esteja perfeitamente correto e também seja executado bem no navegador, eu acho que o erro que você está recebendo é por causa do compilador TypeScript.

Para o código-fonte

interface Animal {
    name: string;
    weight: number
}

class AnimalCollection extends Array <Animal> {
    constructor(name: string, ...items: Animal[]) {
        super(...items);

        Object.defineProperty(this, 'name', {
            enumerable: false,
            writable: false,
            value: name
        })
    }

    findAnimal(name:string): Animal | null {
        return this.find(a => a.name === name) || null;
    }
}

const animalsArray = [
    {name: 'TD-23', weight: 60},
    {name: 'TD-25', weight: 50},
    {name: 'TXD-26', weight: 120},
    {name: 'TYD-26', weight: 40}
];

const animals = new AnimalCollection('Deers', ...animalsArray)

console.log(animals.findAnimal('TD-23'));

Se a opção de destino do compilador estiver definida como ES5, então ele produz código que quebra a implementação. O código que ele gera é

"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __spreadArrays = (this && this.__spreadArrays) || function () {
    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
    for (var r = Array(s), k = 0, i = 0; i < il; i++)
        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
            r[k] = a[j];
    return r;
};
var AnimalCollection = /** @class */ (function (_super) {
    __extends(AnimalCollection, _super);
    function AnimalCollection(name) {
        var items = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            items[_i - 1] = arguments[_i];
        }
        var _this = _super.apply(this, items) || this;
        Object.defineProperty(_this, 'name', {
            enumerable: false,
            writable: false,
            value: name
        });
        return _this;
    }
    AnimalCollection.prototype.findAnimal = function (name) {
        return this.find(function (a) { return a.name === name; }) || null;
    };
    return AnimalCollection;
}(Array));
var animalsArray = [
    { name: 'TD-23', weight: 60 },
    { name: 'TD-25', weight: 50 },
    { name: 'TXD-26', weight: 120 },
    { name: 'TYD-26', weight: 40 }
];
var animals = new (AnimalCollection.bind.apply(AnimalCollection, __spreadArrays([void 0, 'Deers'], animalsArray)))();
console.log(animals.findAnimal('TD-23'));

No entanto, se definirmos o target em tsconfig.json para algo igual ou superior a ES2015, então o código que ele gera é

"use strict";
class AnimalCollection extends Array {
    constructor(name, ...items) {
        super(...items);
        Object.defineProperty(this, 'name', {
            enumerable: false,
            writable: false,
            value: name
        });
    }
    findAnimal(name) {
        return this.find(a => a.name === name) || null;
    }
}
const animalsArray = [
    { name: 'TD-23', weight: 60 },
    { name: 'TD-25', weight: 50 },
    { name: 'TXD-26', weight: 120 },
    { name: 'TYD-26', weight: 40 }
];
const animals = new AnimalCollection('Deers', ...animalsArray);
console.log(animals.findAnimal('TD-23'));

O que, é claro, funciona. Então, eu acho que há um problema no compilador TypeScript para a versão ES5 ou inferior, que quebra a implementação. Eu tentei usar o Babel para a compilação e funciona para ES5.

Resposta #2

Eu posso ver que você estava faltando alguns any, este é o código que funciona para mim:

interface Animal {
    name: string;
    weight: number
}

class AnimalCollection extends Array <Animal> {
    constructor(name: string, ...items : Array <Animal>) { // <-- missing types
        super(...items);

        Object.defineProperty(this, 'name', {
            enumerable: false,
            writable: false,
            value: name
        })
    }

    findAnimal(name : any): Animal|null { // <-- missing null
        return this.find(a => a.name === name) || null;
    }
}

const animalsArray = [
    {name: 'TD-23', weight: 60},
    {name: 'TD-25', weight: 50},
    {name: 'TXD-26', weight: 120},
    {name: 'TYD-26', weight: 40}
];

const animals = new AnimalCollection('Deers', ...animalsArray)

console.log(animals.findAnimal('TD-23'));

No entanto, este código gera o seguinte JS:

"use strict";
class AnimalCollection extends Array {
    constructor(name, ...items) {
        super(...items);
        Object.defineProperty(this, 'name', {
            enumerable: false,
            writable: false,
            value: name
        });
    }
    findAnimal(name) {
        return this.find(a => a.name === name) || null;
    }
}
const animalsArray = [
    { name: 'TD-23', weight: 60 },
    { name: 'TD-25', weight: 50 },
    { name: 'TXD-26', weight: 120 },
    { name: 'TYD-26', weight: 40 }
];
const animals = new AnimalCollection('Deers', ...animalsArray);
console.log(animals.findAnimal('TD-23'));

Que não gera nenhum erro

Artigos relacionados