Иммутабельность — это принцип, согласно которому данные не изменяются после создания. Вместо модификации существующих объектов создаются новые копии с внесёнными изменениями. В контексте Preact это имеет критическое значение, поскольку фреймворк использует виртуальный DOM и диффинг для эффективного обновления интерфейса. Изменяемые объекты могут приводить к непредсказуемым ререндерингам и багам, тогда как иммутабельные данные делают поведение компонентов предсказуемым.
Ключевые моменты иммутабельности:
Пример иммутабельного обновления массива в Preact:
import { h } from 'preact';
import { useState } from 'preact/hooks';
function TodoList() {
const [todos, setTodos] = useState(['Купить хлеб', 'Сделать зарядку']);
const addTodo = (todo) => {
// Создание нового массива вместо модификации существующего
setTodos([...todos, todo]);
};
return (
<div>
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo}</li>
))}
</ul>
<button onCl ick={() => addTodo('Новая задача')}>Добавить</button>
</div>
);
}
Здесь массив todos не изменяется напрямую, создаётся
новая копия с добавленной задачей.
Чистая функция — это функция, которая для одинаковых аргументов всегда возвращает одинаковый результат и не имеет побочных эффектов. В Preact чистые функции особенно важны для компонентов и утилит, используемых при работе с состоянием.
Свойства чистых функций:
Пример чистой функции для обновления состояния:
function toggleItem(items, id) {
return items.map(item =>
item.id === id ? { ...item, completed: !item.completed } : item
);
}
// Использование в компоненте
const [items, setItems] = useState([
{ id: 1, completed: false, title: 'Сделать уроки' }
]);
const handleToggle = (id) => {
setItems(toggleItem(items, id));
};
Функция toggleItem не изменяет исходный массив, а
возвращает новый. Это упрощает работу Preact с виртуальным DOM, так как
изменения легко отслеживать через ссылочные сравнения.
Иммутабельность и чистые функции тесно связаны. Чистая функция облегчает создание иммутабельных структур данных, так как результат функции всегда новый объект без изменения исходного. Это повышает производительность Preact:
Хуки Preact (useState, useReducer)
естественным образом сочетаются с иммутабельными подходами.
useReducer особенно подходит для сложных состояний:
import { h } from 'preact';
import { useReducer } from 'preact/hooks';
function reducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, { id: Date.now(), text: action.text, done: false }];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo
);
default:
return state;
}
}
function TodoApp() {
const [todos, dispatch] = useReducer(reducer, []);
return (
<div>
<button onCl ick={() => dispatch({ type: 'ADD_TODO', text: 'Новая задача' })}>
Добавить задачу
</button>
<ul>
{todos.map(todo => (
<li key={todo.id} onCl ick={() => dispatch({ type: 'TOGGLE_TODO', id: todo.id })}>
{todo.text} {todo.done ? '(выполнено)' : ''}
</li>
))}
</ul>
</div>
);
}
Каждое действие возвращает новый массив задач, не мутируя предыдущий. Это обеспечивает надёжность и предсказуемость состояния приложения.
map,
filter, reduce для создания новых
массивов.Object.assign или spread
{ ...obj } для создания копий с изменениями.useReducer с
чистыми редьюсерами, что упрощает тестирование и поддержку кода.Иммутабельность и чистые функции в Preact создают основу для устойчивого, предсказуемого и производительного кода. Они обеспечивают логическую целостность состояния, сокращают количество ошибок и делают приложение удобным для масштабирования и тестирования.