BonBonSlick
@BonBonSlick
Junior Web Developer Trainee

State machine изоляция мутаций или как правильно управлять состоянием сложных модулей с глубокими обьектами?

У нас есть state = хранилище
getter = только возвращают значения
action = выполняют и содержат всю логику
mutation = только мутирует состояние

Рассмотрим сложную структуру, она может быть сложнее, еще глубже, для примера взят реальный код который все еще дорабатываю.

state
spoiler
export interface IAbstractFormWithLocalizedFields extends IAbstractForm,
                                                          IFormWithLocalizedFieldsGetters,
                                                          IFormWithLocalizedFieldsActions {
    _fields?: {
        _localizedFields?: Array<ILocalizedFields>,
    }
    _violations?: {
        _localizedFields?: Array<ILocalizedFields>,
    }
}

let locales = [
        {
            isDefault:  true,
            isSelected: true,
            locale:     {
                language: {
                    code:       {
                        alpha2: 'en',
                        alpha3: 'eng',
                    },
                    nativeName: 'English',
                    commonName: 'English',
                },
                country:  {
                    code:       {
                        alpha2: 'ua',
                        alpha3: 'ukr',
                    },
                    nativeName: 'Украина',
                    commonName: 'Ukraine',
                },
            },
            fields:     [
                {
                    fieldName: 'title',
                    values:    [
                        {localizedValue: 'Some titles 1', isDefault: true},
                        {localizedValue: 'Some titles 2', isDefault: false},
                        {localizedValue: 'Some titles 3', isDefault: false},
                    ],
                },
                {
                    fieldName: 'description',
                    values:    [
                        {localizedValue: 'Some description 1', isDefault: true},
                    ],
                },
            ],
        },
        {
            isDefault:  false,
            isSelected: false,
            locale:     {
                language: {
                    code:       {
                        alpha2: 'de',
                        alpha3: 'deu',
                    },
                    nativeName: 'Deutch',
                    commonName: 'Deutch',
                },
                country:  {
                    code:       {
                        alpha2: 'ge',
                        alpha3: 'ger',
                    },
                    nativeName: 'Germany',
                    commonName: 'Germany',
                },
            },
            fields:     [
                {
                    fieldName: 'title',
                    values:    [
                        {localizedValue: 'Some titles 11', isDefault: true},
                        {localizedValue: 'Some titles 22', isDefault: false},
                        {localizedValue: 'Some titles 33', isDefault: false},
                    ],
                },
                {
                    fieldName: 'description',
                    values:    [
                        {localizedValue: 'Some description 11', isDefault: true},
                    ],
                },
            ],
        },
    ]
;

export interface ILocalizedFields {
    locale: ILocale,
    isDefault: boolean,
    isSelected: boolean,
    fields: Array<ILocalizedField>,
}

export interface ILocalizedField {
    fieldName: string,
    values: Array<ILocalizedValue>,
}

export interface ILocalizedValue {
    localizedValue: string,
    isDefault: boolean,
}

const local = {
    /** @override **/
    _fields:     {
        _localizedFields: locales,
    },
};

export default (): IStrAny => merge({}, common(), local) as IStrAny;


mutation
spoiler
[SET_ACTIVE_LOCALE_ISO](state: any, activateLocaleWithISO: ISOAlpha2Type): void {
        let fields: Array<ILocalizedFields> = state._fields._localizedFields;

        // can  moved to getter function
        const disableByIndex: number = findIndex(
            state._fields._localizedFields,
            (field: ILocalizedFields): boolean => field.isSelected,
        );
        if (-1 < disableByIndex) {
            let disabledItem: ILocalizedFields = fields[disableByIndex];
            disabledItem.isSelected            = false;
            fields.splice(disableByIndex, 1, disabledItem);
        }

        // can  moved to getter function
        const enableByIndex: number = findIndex(
            state._fields._localizedFields,
            (field: ILocalizedFields): boolean =>
                formatLocaleToISOCode(field.locale).toLowerCase() === activateLocaleWithISO,
        );
        if (-1 < enableByIndex) {
            let enabledItem: ILocalizedFields = fields[enableByIndex];
            enabledItem.isSelected            = true;
            fields.splice(enableByIndex, 1, enabledItem);
        }
    },


action
spoiler
setActiveLocaleISO({state, commit, getters}, isoCode: ISOAlpha2Type): void {
        if (isoCode !== getters.activeLocalizedFieldsLocaleISO2) {
            commit(SET_ACTIVE_LOCALE_ISO, isoCode.toLowerCase());
        }
    },


getter
spoiler
localeLocalizedFieldsIndexByLocaleISO2: (state: any, getters: any) => (localeISO_2: ISOAlpha2Type): ILocalizedFields | undefined => {
        return  findIndex(
            getters.localizedFields  as Array<ILocalizedFields>,
            (field: ILocalizedFields): boolean => localeISO_2 ===  formatLocaleToISOCode(field.locale).toLowerCase(),
        );
    },
    locali


Из примера выше видно что поиск по индексу можно вынести в геттеры, вызвать в екшенах и передать уже найденные индексы по которым произвести модификации в мутации. Но в примере поиск и мутация происходит в мутации.
То есть код поиска и мутации дублируется.

Сейчас работает пример выше, подход который описал тоже будет работать и уменьшит количество кода т.к. логика поиска что мутировать, модифицировать уходит в геттер и передается в мутацию из екшена.
И еще есть подход при котором мы передаем исключительно путь по которому модифицировать, условно
licalizedFieldsSelectedPath (state : any,  paramsForDeepObjSearch: {localeIso2 : string,  fieldName: string} ) : string {
   return '_fields._localizedFields.2.isSelected' // get path we want to update in deep object
}

activateLocalizedFieldsTab (getters: any, dispatch: any) : void {
  dispatch('mutationName', {mutationPath: getters.licalizedFieldsSelectedPath, value: true}); // pass path and value
}

setActiveLocalizedFields (state : any, update: {mutationPath: string, value: any}) : void {
 state[update.mutationPath] = update.value;  // state mutated
}


Мною уже описано 3 подхода и пока я мечусь между ними не могу выбрать пускай и использую то что в примере. Каждый имеет свои плюсы и минусы. К примеру
1 - вариант это текущий, логика геттеров дублируется в мутациях
2 - вариант где геттеры передают индексы которые мутировать. Условно в этом подходе мы можем создать новый обьект в екшене и тупо присвоить новое состояние в мутации что делает ее одностроковой
3 - подход где передаем путь который мутировать, благодаря этому можно увидеть в дебаггере что мутируется

прос / конс
1 - больше кода, но он выходит боле изолированный и менее подвержен внешним изменениям, единственная зависимость это сама структура состояния которая и в геттерах и в мутациях должна быть идентична. Как только где-то попытка доступа или присвоение поля которого нет, лезут баги
2 - выносим всю логику получения данных из состояния в геттеры, меньше кода, больше реюзабилити, но сложнее дебажить. В данном случае скорее всего уже передается новый обьект всего стейта и садится в мутации
3 - тут очень гибко, указываем путь и передаем значение, но вот написание геттеров путей, предвижу тогда геттеров будет много, ведь их надо делать на каждый уровень по методу или как то так, учитывать глубину. К примеру
return stateDeepMapStructure // {level_1_path_1 : '_fields', level_2_path_1: '_localizedFields'}


И так далее, пока в размышлениях какие подходы использовать когда и как.
Если переводить в другую плоскость, то в RDBMS это column type text внутри с json и нам необходимо модифицировать в SQL запросе значение одного поля внутри json N глубины.
NoSql вроде более подходящие для таких структур, но вот пока еще не ковырял как тот же mongo обновляет глубокие поля документов.

UPD. забыл про плоские структуры
spoiler
{
{
  localeCountryISO2Code : 'ru',
  localeLanguageISO2Code : 'ru',
  fieldName: 'title',
  fieldValue: 'Крутой тайтл для крутого поста',
  isSelected:  true,
  isDefault: true,
},
{
  localeCountryISO2Code : 'en',
  localeLanguageISO2Code : 'en',
  fieldName: 'title',
  fieldValue: 'This is cool title for this cool post',
  isSelected:  false,
  isDefault: false,
}
}


Ваши идеи?
Как бы вы управляли такими состояними?
Почему?
  • Вопрос задан
  • 128 просмотров
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы