Matcap menu using React

1+

Однажды я закинул ссылку на свой блог в одну из групп на Facebook, посвященной Three.js. Один из комментаторов сказал мне, что было бы круто, если бы я добавил в редактор Matcap. И я решил – а почему бы и нет.  Но данный пост посвящен больше использованию React нежели созданию Matcap, так как материал Matcab реализован в Three.js и вся задача сводится к тому, чтобы применить этот материал и назначить изображение.

Прежде я старался использовать только Vanilla JS, так как проект учебный. Но и по этой же причине я решил попробовать применить React для части интерфейса, так как изучение React (или какого-то другого из трех китов фронтенда) является неотъемлемой частью обучения web-программиста. Мне было интересно именно внедрить React в существующий проект, самому настроить Webpack, что вполне у меня получилось, конечно, не без преодоления трудностей — при установке React из npm почему-то удалились все модули, которые у меня были установлены до этого, Webpack начал ругаться и проект перестал собираться.

Для связи событий между меню React и редактором мне понадобился state-менеджер. Я умышленно пока не использую готовые решения типа Redux, так как хочу написать сам и понять, как это работает. Нашел урок в интернете и написал свой EventEmitter:

export default class EventEmitter {
    constructor(){
        // singleton
        if (EventEmitter.exist){
            return EventEmitter.instance;
        }
        EventEmitter.instance = this;
        EventEmitter.exist = true;
        //---
        this._events = {};
    }

    onEvent(name, listener) {
        if (!this._events[name]) {
            this._events[name] = [];
        }

        this._events[name].push(listener);
    }

    removelistener(name, listenerToRemove) {
        if (!this._events[name]) {
            throw new Error(`Can't remove a listener. Event "${name} doesn't exist`);
        }

        this._events[name] = this._events[name].filter( listener => listener != listenerToRemove)
    }

    emitEvent(name, data) {
        if (!this._events[name]) {
            throw new Error(`Can't emit an event. Event ${name} doesn't exist`);
        }

        this._events[name].forEach( callback => callback(data));
    }
}

Затем создал класс State, тоже singleton, и реализовал в нем метод

    changeAppState( mode, state ) {
        this.appState = state;
        this.eventEmitter.emitEvent( mode, state )
    }

А в коде сцены слушаю это событие и меняю изображение

this.state.eventEmitter.onEvent('matcapChanged', matcapChanging );

        function matcapChanging(img) {
            if ( !scope.currentSelection.object ) return;
            scope.currentSelection.object.material.dispose();
            scope.currentSelection.object.material = new MeshMatcapMaterial();
            let texture = new TextureLoader().load( "_Resources/Matcabs/Test/" + img )
            scope.currentSelection.object.material.matcap = texture;
        }

Событие установки изображения я добавил в useEffect, то есть когда происходит выбор изображения, происходит рендер компонента ReactPanel, и после рендера, если изображение изменилось, генерируется событие

useEffect( () => {appState.changeAppState( 'matcapChanged', selectedCard.src )}, [selectedCard] );

1+