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

Как понимаешь работу иммутабельных типов данных в коде?

2.0 Middle🔥 192 комментариев
#JavaScript Core

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

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

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

Иммутабельность: философия неизменяемости

Иммутабельность (immutability) — это принцип, при котором данные не изменяются после создания. Вместо изменения существующего объекта мы создаём новый объект с изменённым значением. Это может показаться неэффективным, но это основа предсказуемого кода, упрощает отладку, позволяет React оптимизировать рендеры и предотвращает целый класс багов.

Базовые примеры: мутабельность vs иммутабельность

// ============================================
// НЕПРАВИЛЬНО: Мутабельность (изменение исходного объекта)
// ============================================

// Пример 1: Изменение объекта
const user = { name: 'John', age: 30 };
const updatedUser = user; // Ссылка на тот же объект
updatedUser.age = 31; // Изменяем исходный объект
console.log(user.age); // 31 — сюрприз! Исходный объект изменился

// Пример 2: Изменение массива
const numbers = [1, 2, 3];
numbers.push(4); // Методы push, pop, splice изменяют исходный массив
console.log(numbers); // [1, 2, 3, 4]

// ============================================
// ПРАВИЛЬНО: Иммутабельность (создание нового объекта)
// ============================================

// Пример 1: Изменение объекта через spread оператор
const user = { name: 'John', age: 30 };
const updatedUser = { ...user, age: 31 }; // Создаём новый объект
console.log(user.age); // 30 — исходный объект не изменился
console.log(updatedUser.age); // 31

// Пример 2: Изменение вложенного объекта
const user = {
  name: 'John',
  address: { city: 'NYC', zip: '10001' },
};

// Неправильно (неглубокая копия):
const updated1 = { ...user, address: { ...user.address, city: 'Boston' } };

// Лучше: использовать библиотеку
import { produce } from 'immer';
const updated2 = produce(user, draft => {
  draft.address.city = 'Boston';
});

// Пример 3: Изменение массива
const numbers = [1, 2, 3];
const withNewNumber = [...numbers, 4]; // Spread оператор создаёт новый массив
console.log(numbers); // [1, 2, 3] — исходный массив не изменился
console.log(withNewNumber); // [1, 2, 3, 4]

// Методы для иммутабельной работы с массивами:
const items = [1, 2, 3, 4, 5];

// map — создаёт новый массив с преобразованиями
const doubled = items.map(x => x * 2); // [2, 4, 6, 8, 10]

// filter — создаёт новый массив с отфильтрованными элементами
const evens = items.filter(x => x % 2 === 0); // [2, 4]

// slice — создаёт новый массив (часть исходного)
const subarray = items.slice(1, 3); // [2, 3]

// Неправильно: splice изменяет исходный массив
items.splice(0, 1); // Удаляет первый элемент — мутация!

Иммутабельность в React

import { useState, useCallback } from 'react';

export function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, title: 'Learn React', completed: false },
    { id: 2, title: 'Build app', completed: false },
  ]);

  // НЕПРАВИЛЬНО: изменение исходного массива
  const updateTodoWrong = (id: number, completed: boolean) => {
    const todo = todos.find(t => t.id === id);
    if (todo) {
      todo.completed = completed; // Мутация!
      setTodos([...todos]); // React не заметит изменение
    }
  };

  // ПРАВИЛЬНО: создание нового массива
  const updateTodoRight = useCallback((id: number, completed: boolean) => {
    setTodos(prevTodos =>
      prevTodos.map(todo =>
        todo.id === id ? { ...todo, completed } : todo
      )
    );
  }, []);

  // Добавление нового элемента
  const addTodo = useCallback((title: string) => {
    setTodos(prevTodos => [
      ...prevTodos,
      { id: Date.now(), title, completed: false },
    ]);
  }, []);

  // Удаление элемента
  const deleteTodo = useCallback((id: number) => {
    setTodos(prevTodos => prevTodos.filter(t => t.id !== id));
  }, []);

  return (
    <div>
      {todos.map(todo => (
        <div key={todo.id}>
          <input
            type="checkbox"
            checked={todo.completed}
            onChange={(e) => updateTodoRight(todo.id, e.target.checked)}
          />
          <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
            {todo.title}
          </span>
          <button onClick={() => deleteTodo(todo.id)}>Delete</button>
        </div>
      ))}
      <button onClick={() => addTodo('New task')}>Add</button>
    </div>
  );
}

Использование библиотеки Immer для удобства

import { useState, useCallback } from 'react';
import { produce } from 'immer';

export function UserProfile() {
  const [user, setUser] = useState({
    name: 'John',
    settings: {
      theme: 'dark',
      notifications: {
        email: true,
        sms: false,
      },
    },
  });

  // С Immer: можно писать как-будто мутируешь, а Immer автоматически создаёт новый объект
  const updateTheme = useCallback((theme: string) => {
    setUser(produce(draft => {
      draft.settings.theme = theme;
    }));
  }, []);

  const toggleEmailNotifications = useCallback(() => {
    setUser(produce(draft => {
      draft.settings.notifications.email = !draft.settings.notifications.email;
    }));
  }, []);

  return (
    <div>
      <p>Theme: {user.settings.theme}</p>
      <button onClick={() => updateTheme('light')}>Switch to Light</button>
      <label>
        <input
          type="checkbox"
          checked={user.settings.notifications.email}
          onChange={toggleEmailNotifications}
        />
        Email notifications
      </label>
    </div>
  );
}

Почему иммутабельность важна

1. Оптимизация React: React сравнивает старый и новый state через === (сравнение ссылок). Если создасть новый объект, React вычислит, что данные изменились.

2. Предсказуемость: Когда данные не меняются, легче отследить, что произошло. Это упрощает отладку и тестирование.

3. Истории изменений: Если ты сохраняешь старые объекты, можешь реализовать undo/redo.

4. Параллелизм: В языках вроде JavaScript с одним потоком иммутабельность помогает избежать race conditions.