← Назад к вопросам

Как реализовывал изменение свойства в компоненте и в хранилище?

2.0 Middle🔥 201 комментариев
#React

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Управление изменением состояния: от компонента до хранилища

В современных React-приложениях изменение свойства (состояния) и его синхронизация между компонентом и хранилищем — это комплексный процесс, который я реализую через четкое разделение ответственности и применение проверенных архитектурных паттернов.

Основные подходы к изменению состояния

Я выделяю три ключевых уровня управления изменениями:

  1. Локальное состояние компонента (useState, useReducer)
  2. Глобальное состояние приложения (Redux, MobX, Zustand, Context API)
  3. Серверное состояние (React Query, SWR, RTK Query)

Типичный workflow изменения свойства

Рассмотрим полный цикл на примере изменения свойства userTheme в приложении:

// Компонент пользовательских настроек
import { useState } from 'react';
import { useDispatch } from 'react-redux';
import { updateUserTheme } from '../store/userSlice';

const ThemeSettings = ({ userId }) => {
  const [localTheme, setLocalTheme] = useState('light');
  const dispatch = useDispatch();
  
  const handleThemeChange = async (newTheme) => {
    // 1. Оптимистичное обновление UI
    setLocalTheme(newTheme);
    
    try {
      // 2. Диспатч действия в хранилище
      dispatch(updateUserTheme({
        userId,
        theme: newTheme
      }));
      
      // 3. Синхронизация с сервером (если нужно)
      await api.updateUserSettings(userId, { theme: newTheme });
      
    } catch (error) {
      // 4. Откат при ошибке
      setLocalTheme('light'); // Возвращаем предыдущую тему
      dispatch(rollbackThemeChange(userId));
    }
  };
  
  return (
    <select 
      value={localTheme} 
      onChange={(e) => handleThemeChange(e.target.value)}
    >
      <option value="light">Светлая</option>
      <option value="dark">Темная</option>
    </select>
  );
};

Паттерны реализации изменений

1. Односторонний поток данных (Flux/Redux паттерн)

// actionTypes.js
export const UPDATE_USER_PROFILE = 'UPDATE_USER_PROFILE';

// actions.js
export const updateUserProfile = (profileData) => ({
  type: UPDATE_USER_PROFILE,
  payload: profileData
});

// reducer.js
const userReducer = (state = initialState, action) => {
  switch (action.type) {
    case UPDATE_USER_PROFILE:
      return {
        ...state,
        ...action.payload,
        lastUpdated: Date.now()
      };
    default:
      return state;
  }
};

2. Современный подход с Redux Toolkit

// userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const updateUserProperty = createAsyncThunk(
  'user/updateProperty',
  async (propertyData, { rejectWithValue }) => {
    try {
      const response = await api.updateUser(propertyData);
      return response.data;
    } catch (error) {
      return rejectWithValue(error.response.data);
    }
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    setProperty: (state, action) => {
      // Immer позволяет мутировать state "как бы" иммутабельно
      state[action.payload.key] = action.payload.value;
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(updateUserProperty.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(updateUserProperty.fulfilled, (state, action) => {
        state.status = 'succeeded';
        Object.assign(state, action.payload);
      })
      .addCase(updateUserProperty.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.payload;
      });
  }
});

3. Реактивный подход с MobX

// userStore.js
import { makeAutoObservable, runInAction } from 'mobx';

class UserStore {
  theme = 'light';
  isLoading = false;
  
  constructor() {
    makeAutoObservable(this);
  }
  
  // Action для изменения свойства
  setTheme(newTheme) {
    this.theme = newTheme;
  }
  
  // Асинхронное действие
  async updateTheme(newTheme) {
    this.isLoading = true;
    try {
      await api.updateTheme(newTheme);
      runInAction(() => {
        this.theme = newTheme;
        this.isLoading = false;
      });
    } catch (error) {
      runInAction(() => {
        this.isLoading = false;
        this.error = error.message;
      });
    }
  }
}

// Использование в компоненте
const ThemeToggle = observer(() => {
  const { userStore } = useStores();
  
  return (
    <button 
      onClick={() => userStore.updateTheme('dark')}
      disabled={userStore.isLoading}
    >
      Сменить тему
    </button>
  );
});

Ключевые принципы, которые я применяю:

  1. Единственный источник истины — состояние живет в хранилище, компоненты только отображают его
  2. Иммутабельность — особенно важно для Redux, чтобы корректно работали механизмы сравнения
  3. Селекторы — для вычисляемых значений и мемоизации:
// selectors.js
import { createSelector } from '@reduxjs/toolkit';

const selectUser = (state) => state.user;

export const selectUserTheme = createSelector(
  [selectUser],
  (user) => user.theme || 'default'
);
  1. Оптимистичные обновления — для улучшения UX, с последующим откатом при ошибках
  2. Нормализация данных — особенно для сложных вложенных структур
  3. Middleware для side effects — redux-thunk, redux-saga, или встроенные решения в RTK

Сложные сценарии и лучшие практики

Для форм часто использую гибридный подход: локальное состояние для быстрого отклика UI + синхронизация с хранилищем по таймеру или при blur:

const UserForm = () => {
  const [localData, setLocalData] = useState({});
  const dispatch = useDispatch();
  const debouncedSave = useDebouncedCallback(() => {
    dispatch(updateUser(localData));
  }, 500);
  
  const handleChange = (field, value) => {
    setLocalData(prev => ({ ...prev, [field]: value }));
    debouncedSave();
  };
  
  // ... render form
};

Для реализации отмены/повтора (undo/redo) применяю паттерн Command или хранение истории изменений в хранилище.

Важные нюансы:

  • Всегда учитываю race conditions при асинхронных операциях
  • Использую TypeScript для типизации actions и состояния
  • Для больших приложений применяю code splitting для редьюсеров
  • Тестирую редьюсеры как чистые функции и actions через integration tests

Этот многоуровневый подход обеспечивает предсказуемость изменений состояния, легкую отладку через инструменты вроде Redux DevTools, и масштабируемость приложения по мере роста его сложности.

Как реализовывал изменение свойства в компоненте и в хранилище? | PrepBro