(псевдо)Наследование для компонентов ReactJS

в 12:26, , рубрики: javascript, ReactJS, ооп

Я хочу написать коротенький пост, про то, как я решил проблему наследования в ReactJS. Обычно, на форумах, люди советуют использовать миксины для наследования функционала, но, по-моему, это не совсем правильно. Все-таки трэйты/миксины и классы это не одно и то же, да еще и из-за возможности множественного наследования могут возникать вот такие казусы:

var A = {
    doStuff (){}
}

var B = {
    doStuff (){}
}

var C = React.createClass({
    mixins: [A, B]
});
//упс... ошибка, потому что React не может решить какой из doStuff унаследовать

К тому же, миксины не позволяют делать стандартные ООПешные фишки вроде перезаписи методов(method override):

var A = {
    doStuff (){}
}

var C = React.createClass({
    mixins: [A],
    doStuff (){
         //неа, не получится
    }
});

А без этого, естественно, не сработает и расширение функционала как во «взрослых» ООП языках:

doStuff (){
    super.doStuff()
    //дополнительный функционал
}

Конечно, классы ES6 решат эту проблему, и команда ReactJS к этому готовится, если судить по постам на их блоге, но ждать ES6 придется как второго пришествия, а затем, придется еще подождать пока не вымрут старые Интернет Эксплореры без поддержки ES6.
Итак, я хочу предложить вам альтернативный метод, который и сам использую, но для этого вам понадобятся:
1) Система модулей/зависимостей: RequireJS/Browserify/WebPack/что там еще сейчас в моде. Если вы не пользуетесь/не знаете что такое JavaScript модули, что ж, самое время узнать.
2) Какая-нибудь функция/либа для глубокого копирования объектов, например, jQuery.extend, _.extend и т.п.

Итак, я пишу модули своих компонент следующим образом:

var React = require('react');
var Human = {
    whoAreYou (){
         return "I'm a human";
    }

    whatDoYouDo (){
        return "I'm just chilling";
    }

    render (){
         return (
              <h1>{this.whoAreYou()}<small>{this.whatDoYouDo()}</small></h1>
         )
    }
}

module.exports = {
     Class: Human,
     Component: React.createClass(Human)
}

Фишка в том, что я экспортирую не только компоненту, но и «чистый» объект, из которого эта компонента создается, таким образом, когда мне надо использовать просто компоненту <Human/>, я беру поле Component из экспорта моего модуля:

var Human = require('human').Component;

А вот когда мне надо от моего модуля унаследовать, и тут начинается самое интересное, я использую поле Class:

var React = require('react');
var Parent = require('human').Class;
var Programmer = {};
jQuery.extend(true, Programmer, Parent, {
    whoAreYou (){
         return Parent.whoAreYou.apply(this) + " and a programmer";//вызов метода из родителя!
    }
    
    whatDoYouDo (){
        //перезапись метода полностью
       return "I write code";
    }

    drinkCoffee (){
        //добавление нового метода
        console.log('*sip*');
    }
});

Естественно, этот модуль я тоже экспортирую согласно вышеописанной «конвенции»:

module.exports = {
    Class: Programmer,
    Component: React.createClass(Programmer)
}

И теперь его можно использовать в приложении:

var Programmer = require('programmer').Component;

Ну, или наследовать/расширять дальше, например, в JuniorDeveloper.
И на этом все, это был мой коротенький пост про костыль для (псевдо)наследования в ReactJS. Успешной вам трудовой недели, господа!

Автор: diogene

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js