React Hooks произвели революцию в разработке React-приложений, предоставив разработчикам мощный инструмент для управления состоянием и жизненным циклом компонентов. В этом подробном руководстве читатель узнает, как создать свой первый пользовательский React Hook, шаг за шагом.
Что такое React Hooks?
Прежде чем погрузиться в процесс создания пользовательского хука, важно понять, что представляют собой React Hooks и почему они так важны.
React Hooks — это функции, которые позволяют разработчикам «подключаться» к состоянию и жизненному циклу React из функциональных компонентов. Они были введены в React 16.8 и предоставили способ использовать состояние и другие возможности React без написания классов.
Основные встроенные хуки React
Перед созданием собственного хука полезно ознакомиться с некоторыми из наиболее часто используемых встроенных хуков:
- useState: Позволяет добавлять состояние в функциональные компоненты.
- useEffect: Используется для выполнения побочных эффектов в функциональных компонентах.
- useContext: Предоставляет доступ к контексту React.
- useReducer: Альтернатива useState для управления сложным состоянием.
- useCallback: Возвращает мемоизированную версию колбэк-функции.
- useMemo: Мемоизирует вычисляемое значение.
Подготовка к созданию пользовательского хука
Перед тем как приступить к созданию первого пользовательского хука, необходимо убедиться, что у разработчика есть все необходимые инструменты и знания.
Необходимые предварительные знания
Для успешного создания и использования пользовательских хуков, разработчику следует иметь:
- Базовое понимание JavaScript и ES6+
- Опыт работы с React и функциональными компонентами
- Знакомство с основными встроенными хуками React
- Понимание концепции замыканий в JavaScript
Настройка среды разработки
Для начала работы над пользовательским хуком потребуется следующее:
- Node.js и npm (или yarn) установленные на компьютере
- Редактор кода (например, Visual Studio Code, Sublime Text или WebStorm)
- Создание нового React-проекта или использование существующего
Если у разработчика еще нет React-проекта, он может создать его с помощью Create React App:
npx create-react-app custom-hook-tutorial cd custom-hook-tutorial npm start
Шаг 1: Определение цели пользовательского хука
Первым шагом в создании пользовательского хука является определение его назначения. Пользовательский хук должен решать конкретную задачу или предоставлять переиспользуемую логику, которую можно применить в различных компонентах.
Для этого руководства будет создан пользовательский хук useToggle, который будет управлять булевым состоянием и предоставлять функцию для его переключения.
Применение useToggle
Хук useToggle может быть полезен в различных сценариях, например:
- Переключение видимости модальных окон
- Управление состоянием «развернуть/свернуть» для аккордеонов
- Включение/выключение определенных функций интерфейса
Шаг 2: Создание файла для пользовательского хука
Следующим шагом является создание отдельного файла для пользовательского хука. Это поможет организовать код и сделает хук легко импортируемым в другие компоненты.
В директории src проекта следует создать новую папку hooks и внутри нее файл useToggle.js:
mkdir src/hooks touch src/hooks/useToggle.js
Шаг 3: Написание базовой структуры хука
Теперь можно приступить к написанию базовой структуры хука useToggle. Вот начальный код для файла useToggle.js:
import { useState } from 'react';
const useToggle = (initialState = false) => {
const [state, setState] = useState(initialState);
const toggle = () => {
setState(prevState => !prevState);
};
return [state, toggle];
};
export default useToggle;
Давайте разберем этот код:
- Импортируется хук useState из React для управления состоянием.
- Определяется функция useToggle, которая принимает необязательный параметр initialState со значением по умолчанию false.
- Внутри хука используется useState для создания состояния и функции его обновления.
- Определяется функция toggle, которая инвертирует текущее состояние.
- Хук возвращает массив, содержащий текущее состояние и функцию toggle.
Шаг 4: Использование пользовательского хука в компоненте
Теперь, когда базовая структура хука готова, можно использовать его в компоненте. Для демонстрации создадим простой компонент ToggleButton.
Создайте новый файл src/components/ToggleButton.js со следующим содержимым:
import React from 'react'; import useToggle from '../hooks/useToggle';
const ToggleButton = () => {
const [isOn, toggleIsOn] = useToggle();
return (
);
};
export default ToggleButton;
В этом компоненте:
- Импортируется пользовательский хук useToggle.
- Используется деструктуризация для получения текущего состояния (isOn) и функции переключения (toggleIsOn).
- Создается кнопка, которая отображает текущее состояние и вызывает функцию toggleIsOn при нажатии.
Шаг 5: Тестирование пользовательского хука
Чтобы убедиться, что пользовательский хук работает корректно, следует протестировать его в реальном приложении. Обновите файл src/App.js, чтобы включить компонент ToggleButton:
import React from 'react'; import ToggleButton from './components/ToggleButton';
function App() {
return (
Custom Hook: useToggle
);
}
export default App;
Теперь при запуске приложения должна отображаться кнопка, которая переключается между состояниями «ON» и «OFF» при нажатии.
Шаг 6: Расширение функциональности хука
После успешного создания и тестирования базовой версии хука useToggle, можно рассмотреть возможности его расширения для большей гибкости и функциональности.
Добавление пользовательских значений
Вместо использования фиксированных значений true и false, можно позволить пользователям задавать собственные значения для состояний «включено» и «выключено».
Обновите файл useToggle.js следующим образом:
import { useState } from 'react';
const useToggle = (initialValue = false, onValue = true, offValue = false) => {
const [value, setValue] = useState(initialValue);
const toggle = () => {
setValue(currentValue =>
currentValue === onValue ? offValue : onValue
);
};
return [value, toggle];
};
export default useToggle;
Теперь хук принимает три параметра:
- initialValue: начальное значение (по умолчанию false)
- onValue: значение для состояния «включено» (по умолчанию true)
- offValue: значение для состояния «выключено» (по умолчанию false)
Использование расширенного хука
Обновите компонент ToggleButton, чтобы использовать новые возможности хука:
import React from 'react'; import useToggle from '../hooks/useToggle';
const ToggleButton = () => {
const [mood, toggleMood] = useToggle('😊', '😊', '😢');
return (
);
};
export default ToggleButton;
Теперь кнопка будет переключаться между смайликами «😊» и «😢» вместо «ON» и «OFF».
Шаг 7: Добавление дополнительных функций
Для еще большей гибкости можно добавить в хук дополнительные функции, такие как принудительная установка значения в определенное состояние.
Обновите файл useToggle.js следующим образом:
import { useState, useCallback } from 'react';
const useToggle = (initialValue = false, onValue = true, offValue = false) => {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => {
setValue(currentValue =>
currentValue === onValue ? offValue : onValue
);
}, [onValue, offValue]);
const setOn = useCallback(() => setValue(onValue), [onValue]);
const setOff = useCallback(() => setValue(offValue), [offValue]);
return [value, toggle, setOn, setOff];
};
export default useToggle;
В этой версии хука:
- Добавлены функции setOn и setOff для прямой установки состояния.
- Использован хук useCallback для мемоизации функций, чтобы избежать ненужных ререндеров.
Использование расширенного хука с дополнительными функциями
Теперь можно обновить компонент ToggleButton, чтобы использовать новые функции:
import React from 'react'; import useToggle from '../hooks/useToggle';
const ToggleButton = () => {
const [mood, toggleMood, setHappy, setSad] = useToggle('😐', '😊', '😢');
return (
Current mood: {mood}
);
};
export default ToggleButton;
Шаг 8: Документирование пользовательского хука
Важным аспектом создания пользовательского хука является его документирование. Это поможет другим разработчикам (или вам самим в будущем) понять, как использовать хук и какие у него есть возможности.
Добавьте JSDoc комментарии к файлу useToggle.js:
import { useState, useCallback } from 'react';
/**
Пользовательский хук для переключения между двумя значениями.
@param {*} initialValue - Начальное значение состояния.
@param {*} onValue - Значение для состояния "включено".
@param {*} offValue - Значение для состояния "выключено".
@returns {Array} Массив, содержащий:
Текущее значение состояния
Функция для переключения состояния
Функция для установки состояния в значение "включено"
Функция для установки состояния в значение "выключено" */ const useToggle = (initialValue = false, onValue = true, offValue = false) => { const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => {
setValue(currentValue =>
currentValue === onValue ? offValue : onValue
);
}, [onValue, offValue]);
const setOn = useCallback(() => setValue(onValue), [onValue]);
const setOff = useCallback(() => setValue(offValue), [offValue]);
return [value, toggle, setOn, setOff];
};
export default useToggle;
Шаг 9: Оптимизация производительности
Хотя наш хук уже довольно эффективен, можно рассмотреть дополнительные способы оптимизации его производительности, особенно для случаев, когда он используется в компонентах, которые часто перерендериваются.
Использование useMemo
Можно использовать хук useMemo для мемоизации возвращаемого массива, чтобы предотвратить ненужные перерендеры компонентов, использующих этот хук:
import { useState, useCallback, useMemo } from 'react';
const useToggle = (initialValue = false, onValue = true, offValue = false) => {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => {
setValue(currentValue =>
currentValue === onValue ? offValue : onValue
);
}, [onValue, offValue]);
const setOn = useCallback(() => setValue(onValue), [onValue]);
const setOff = useCallback(() => setValue(offValue), [offValue]);
return useMemo(() => [value, toggle, setOn, setOff], [value, toggle, setOn, setOff]);
};
export default useToggle;
Шаг 10: Обработка ошибок и валидация
Для повышения надежности хука можно добавить обработку ошибок и валидацию входных параметров. Это поможет предотвратить неправильное использование хука и облегчит отладку.
import { useState, useCallback, useMemo } from 'react';
const useToggle = (initialValue = false, onValue = true, offValue = false) => {
if (onValue === offValue) {
throw new Error('onValue и offValue не должны быть одинаковыми');
}
const [value, setValue] = useState(() => {
if (initialValue !== onValue && initialValue !== offValue) {
console.warn('initialValue должно быть равно onValue или offValue. Используется значение по умолчанию.');
return offValue;
}
return initialValue;
});
const toggle = useCallback(() => {
setValue(currentValue =>
currentValue === onValue ? offValue : onValue
);
}, [onValue, offValue]);
const setOn = useCallback(() => setValue(onValue), [onValue]);
const setOff = useCallback(() => setValue(offValue), [offValue]);
return useMemo(() => [value, toggle, setOn, setOff], [value, toggle, setOn, setOff]);
};
export default useToggle;
Шаг 11: Создание unit-тестов
Тестирование является важной частью разработки надежных и поддерживаемых хуков. Давайте создадим несколько unit-тестов для нашего хука useToggle.
Создайте файл useToggle.test.js в той же директории, где находится useToggle.js:
import { renderHook, act } from '@testing-library/react-hooks'; import useToggle from './useToggle';
describe('useToggle', () => {
test('should initialize with default values', () => {
const { result } = renderHook(() => useToggle());
expect(result.current[0]).toBe(false);
});
test('should toggle between true and false', () => {
const { result } = renderHook(() => useToggle());
act(() => {
result.current1;
});
expect(result.current[0]).toBe(true);
act(() => {
result.current1;
});
expect(result.current[0]).toBe(false);
});
test('should set on and off values', () => {
const { result } = renderHook(() => useToggle(false, 'on', 'off'));
expect(result.current[0]).toBe('off');
act(() => {
result.current2; // setOn
});
expect(result.current[0]).toBe('on');
act(() => {
result.current3; // setOff
});
expect(result.current[0]).toBe('off');
});
test('should throw error if onValue equals offValue', () => {
expect(() => {
renderHook(() => useToggle(false, 'same', 'same'));
}).toThrow('onValue и offValue не должны быть одинаковыми');
});
});
Для запуска этих тестов потребуется установить необходимые зависимости:
npm install --save-dev @testing-library/react-hooks
Шаг 12: Интеграция с TypeScript
Если проект использует TypeScript, можно добавить типизацию для хука useToggle, чтобы улучшить поддержку IDE и обеспечить дополнительную проверку типов.
Переименуйте файл useToggle.js в useToggle.ts и обновите его содержимое:
import { useState, useCallback, useMemo } from 'react';
type ToggleValue = any;
interface UseToggleReturn {
value: ToggleValue;
toggle: () => void;
setOn: () => void;
setOff: () => void;
}
function useToggle(
initialValue: ToggleValue = false,
onValue: ToggleValue = true,
offValue: ToggleValue = false
): UseToggleReturn {
if (onValue === offValue) {
throw new Error('onValue и offValue не должны быть одинаковыми');
}
const [value, setValue] = useState
if (initialValue !== onValue && initialValue !== offValue) {
console.warn('initialValue должно быть равно onValue или offValue. Используется значение по умолчанию.');
return offValue;
}
return initialValue;
});
const toggle = useCallback(() => {
setValue(currentValue =>
currentValue === onValue ? offValue : onValue
);
}, [onValue, offValue]);
const setOn = useCallback(() => setValue(onValue), [onValue]);
const setOff = useCallback(() => setValue(offValue), [offValue]);
return useMemo(() => ({
value,
toggle,
setOn,
setOff
}), [value, toggle, setOn, setOff]);
}
export default useToggle;
Шаг 13: Создание примеров использования
Чтобы помочь другим разработчикам понять, как использовать хук useToggle, можно создать несколько примеров использования в различных сценариях.
Пример 1: Простое переключение
import React from 'react'; import useToggle from './useToggle';
const SimpleToggle = () => {
const { value, toggle } = useToggle();
return (
);
};
export default SimpleToggle;
Пример 2: Переключение с пользовательскими значениями
import React from 'react'; import useToggle from './useToggle';
const CustomValueToggle = () => {
const { value, toggle } = useToggle('Привет', 'Привет', 'Пока');
return (
);
};
export default CustomValueToggle;
Пример 3: Использование setOn и setOff
import React from 'react'; import useToggle from './useToggle';
const ControlledToggle = () => {
const { value, toggle, setOn, setOff } = useToggle();
return (
Состояние: {value ? 'Включено' : 'Выключено'}
);
};
export default ControlledToggle;
Шаг 14: Публикация хука
Если хук получился достаточно универсальным и полезным, его можно опубликовать как отдельный npm-пакет, чтобы другие разработчики могли легко использовать его в своих проектах.
Подготовка к публикации
- Создайте новую директорию для пакета и инициализируйте npm-проект:
mkdir use-toggle-hook cd use-toggle-hook npm init -y
- Скопируйте файл useToggle.ts в новую директорию.
- Создайте файл index.ts:
export { default } from './useToggle';
- Обновите package.json:
{ "name": "use-toggle-hook", "version": "1.0.0", "description": "A custom React hook for toggling between two values", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { "build": "tsc", "prepublishOnly": "npm run build" }, "keywords": ["react", "hook", "toggle", "custom-hook"], "author": "Your Name", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "devDependencies": { "@types/react": "^18.0.0", "typescript": "^4.5.0" } }
- Создайте файл tsconfig.json:
{ "compilerOptions": { "target": "es5", "module": "commonjs", "declaration": true, "outDir": "./dist", "strict": true, "jsx": "react", "esModuleInterop": true }, "include": ["src"], "exclude": ["node_modules", "**/__tests__/*"] }
Публикация пакета
- Убедитесь, что у вас есть аккаунт на npmjs.com
- Войдите в свой npm-аккаунт в терминале:
npm login
- Опубликуйте пакет:
npm publish
Заключение
Создание пользовательского React Hook — это мощный способ инкапсулировать и переиспользовать логику в React-приложениях. Следуя этому пошаговому руководству, разработчик может создать свой первый пользовательский хук, протестировать его, оптимизировать и даже опубликовать для использования другими разработчиками.
Вот краткое резюме основных шагов, которые были рассмотрены:
- Определение цели хука
- Создание базовой структуры
- Реализация основной логики
- Тестирование хука в компоненте
- Расширение функциональности
- Оптимизация производительности
- Добавление обработки ошибок и валидации
- Написание unit-тестов
- Интеграция с TypeScript
- Создание примеров использования
- Публикация хука как npm-пакета
Создание пользовательских хуков — это навык, который совершенствуется с практикой. Чем больше хуков создает разработчик, тем лучше он понимает, как эффективно инкапсулировать и переиспользовать логику в React-приложениях.
Рекомендуется продолжать экспериментировать с созданием различных пользовательских хуков для решения конкретных задач в проектах. Это не только улучшит код, но и поможет глубже понять принципы работы React и функционального программирования в целом.
Дальнейшие шаги
После освоения создания базовых пользовательских хуков, можно рассмотреть следующие направления для развития:
- Создание более сложных хуков, комбинирующих несколько встроенных хуков React
- Изучение паттернов композиции хуков
- Исследование возможностей оптимизации производительности с помощью хуков
- Создание хуков для работы с внешними API и библиотеками
- Разработка хуков для управления сложными формами
- Изучение взаимодействия хуков с контекстом React
Помните, что создание эффективных и полезных пользовательских хуков требует глубокого понимания принципов React и функционального программирования. Продолжайте практиковаться, изучать документацию React и следить за лучшими практиками в сообществе разработчиков.