Полезные хуки React, которые вы можете использовать в своих проектах
Опубликовано: 2022-03-10Хуки — это просто функции, которые позволяют вам подключаться к функциям React или использовать их . Они были представлены на конференции React Conf 2018 для решения трех основных проблем компонентов классов: ада оберток, огромных компонентов и запутанных классов. Хуки дают силу функциональным компонентам React, позволяя разрабатывать с их помощью целое приложение.
Вышеупомянутые проблемы компонентов класса связаны, и решение одной без другой может привести к возникновению дополнительных проблем. К счастью, хуки решили все проблемы просто и эффективно, освободив место для более интересных функций в React. Хуки не заменяют уже существующие концепции и классы React, они просто предоставляют API для прямого доступа к ним.
Команда React представила несколько хуков в React 16.8. Однако вы также можете использовать хуки от сторонних поставщиков в своем приложении или даже создать собственный хук. В этом уроке мы рассмотрим некоторые полезные хуки в React и способы их использования. Мы рассмотрим несколько примеров кода для каждого хука, а также рассмотрим, как создать собственный хук.
Примечание. Для этого руководства требуется базовое понимание Javascript (ES6+) и React.
Мотивация крючков
Как говорилось ранее, хуки были созданы для решения трех проблем: ада оберток, огромных компонентов и запутанных классов. Давайте рассмотрим каждый из них более подробно.
Обертка Ад
Сложные приложения, созданные с использованием компонентов класса, легко попадают в ад оболочек. Если вы изучите приложение в React Dev Tools, вы заметите глубоко вложенные компоненты. Это очень затрудняет работу с компонентами или их отладку. Хотя эти проблемы можно решить с помощью компонентов более высокого порядка и свойств рендеринга , они требуют от вас небольшой модификации кода. Это может привести к путанице в сложном приложении.
Хуками легко делиться, вам не нужно изменять свои компоненты перед повторным использованием логики.
Хорошим примером этого является использование Redux connect
Higher Order Component (HOC) для подписки на магазин Redux. Как и все HOC, чтобы использовать HOC подключения, вы должны экспортировать компонент вместе с определенными функциями более высокого порядка. В случае с connect
у нас будет что-то в этом роде.
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
Где mapStateToProps
и mapDispatchToProps
— это функции, которые необходимо определить.
В то время как в эпоху хуков можно легко добиться того же результата аккуратно и лаконично, используя хуки Redux useSelector
и useDispatch
.
Огромные компоненты
Компоненты класса обычно содержат побочные эффекты и логику с отслеживанием состояния. По мере усложнения приложения компонент часто становится беспорядочным и запутанным. Это связано с тем, что ожидается, что побочные эффекты будут организованы методами жизненного цикла, а не функциональностью. Хотя можно разделить компоненты и упростить их, это часто приводит к более высокому уровню абстракции.
Хуки организуют побочные эффекты по функциональности, и компонент можно разделить на части в зависимости от функциональности.
Запутанные классы
Классы, как правило, более сложная концепция, чем функции. Компоненты React на основе классов многословны и немного сложны для начинающих. Если вы новичок в Javascript, вам будет проще начать работу с функциями из-за их упрощенного синтаксиса по сравнению с классами. Синтаксис может сбивать с толку; иногда можно забыть привязать обработчик событий, что может нарушить код.
React решает эту проблему с помощью функциональных компонентов и хуков, позволяя разработчикам сосредоточиться на проекте, а не на синтаксисе кода.
Например, следующие два компонента React дадут точно такой же результат.
import React, { Component } from "react"; export default class App extends Component { constructor(props) { super(props); this.state = { num: 0 }; this.incrementNumber = this.incrementNumber.bind(this); } incrementNumber() { this.setState({ num: this.state.num + 1 }); } render() { return ( <div> <h1>{this.state.num}</h1> <button onClick={this.incrementNumber}>Increment</button> </div> ); } }
import React, { useState } from "react"; export default function App() { const [num, setNum] = useState(0); function incrementNumber() { setNum(num + 1); } return ( <div> <h1>{num}</h1> <button onClick={incrementNumber}>Increment</button> </div> ); }
Первый пример — это компонент на основе классов, а второй — функциональный компонент. Несмотря на то, что это простой пример, обратите внимание на то, насколько фальшивым является первый пример по сравнению со вторым.
Конвенция и правила хуков
Прежде чем углубляться в различные хуки, было бы полезно взглянуть на соглашение и правила, которые к ним применяются. Вот некоторые правила, применимые к хукам.
- Соглашение об именах хуков должно начинаться с префикса
use
. Таким образом, у нас могут бытьuseState
,useEffect
и т. д. Если вы используете современные редакторы кода, такие как Atom и VSCode, плагин ESLint может оказаться очень полезной функцией для хуков React. Плагин предоставляет полезные предупреждения и советы по передовому опыту. - Хуки должны вызываться на верхнем уровне компонента перед оператором return. Их нельзя вызывать внутри условного оператора, цикла или вложенных функций.
- Хуки должны вызываться из функции React (внутри компонента React или другого хука). Его не следует вызывать из функции Vanilla JS.
Хук useState
useState
— самый простой и полезный хук React. Как и другие встроенные хуки, этот хук должен быть импортирован из react
, чтобы использоваться в нашем приложении.
import {useState} from 'react'
Чтобы инициализировать состояние, мы должны объявить состояние и его функцию обновления и передать начальное значение.
const [state, updaterFn] = useState('')
Мы можем называть наше состояние и функцию обновления как угодно, но по соглашению первый элемент массива будет нашим состоянием, а второй элемент будет функцией обновления. Общепринятой практикой является префикс нашей функции обновления с набором префиксов, за которым следует имя нашего состояния в форме верблюжьего регистра.
Например, давайте установим состояние для хранения значений счетчика.
const [count, setCount] = useState(0)
Обратите внимание, что начальное значение нашего состояния count
установлено на 0
, а не на пустую строку. Другими словами, мы можем инициализировать наше состояние любыми переменными JavaScript, а именно числами, строками, логическими значениями, массивами, объектами и даже BigInt. Существует четкая разница между установками состояний с помощью хука useState
и состояний компонентов на основе классов. Примечательно, что хук useState
возвращает массив, также известный как переменные состояния, и в приведенном выше примере мы деструктурировали массив на state
и функцию updater
.
Рендеринг компонентов
Установка состояний с помощью хука useState
вызывает повторную визуализацию соответствующего компонента. Однако это происходит только в том случае, если React обнаруживает разницу между предыдущим или старым состоянием и новым состоянием. React выполняет сравнение состояний, используя алгоритм Javascript Object.is
.
Установка состояний с помощью useState
Наше состояние count
можно установить на новые значения состояния, просто передав новое значение функции обновления setCount
следующим образом setCount(newValue)
.
Этот метод работает, когда мы не хотим ссылаться на предыдущее значение состояния. Если мы хотим это сделать, нам нужно передать функцию в функцию setCount
.
Предполагая, что мы хотим добавить 5 к нашей переменной count
каждый раз, когда нажимается кнопка, мы могли бы сделать следующее.
import {useState} from 'react' const CountExample = () => { // initialize our count state const [count, setCount] = useState(0) // add 5 to to the count previous state const handleClick = () =>{ setCount(prevCount => prevCount + 5) } return( <div> <h1>{count} </h1> <button onClick={handleClick}>Add Five</button> </div> ) } export default CountExample
В приведенном выше коде мы сначала импортировали хук useState
из react
, а затем инициализировали состояние count
со значением по умолчанию 0. Мы создали обработчик onClick
для увеличения значения count
на 5 при каждом нажатии кнопки. Затем мы отображали результат в теге h1
.
Настройка массивов и состояний объектов
Состояния для массивов и объектов можно задавать почти так же, как и для других типов данных. Однако, если мы хотим сохранить уже существующие значения, нам нужно использовать оператор расширения ES6 при установке состояний.
Оператор распространения в Javascript используется для создания нового объекта из уже существующего объекта. Это полезно здесь, потому что React
сравнивает состояния с операцией Object.is
и затем соответствующим образом перерисовывает.
Давайте рассмотрим приведенный ниже код для установки состояний при нажатии кнопки.
import {useState} from 'react' const StateExample = () => { //initialize our array and object states const [arr, setArr] = useState([2, 4]) const [obj, setObj] = useState({num: 1, name: 'Desmond'}) // set arr to the new array values const handleArrClick = () =>{ const newArr = [1, 5, 7] setArr([...arr, ...newArr]) } // set obj to the new object values const handleObjClick = () =>{ const newObj = {name: 'Ifeanyi', age: 25} setObj({...obj, ...newObj}) } return( <div> <button onClick ={handleArrClick}>Set Array State</button> <button onClick ={handleObjClick}>Set Object State</button> </div> ) } export default StateExample
В приведенном выше коде мы создали два состояния arr
и obj
и инициализировали их некоторыми значениями массива и объекта соответственно. Затем мы создали обработчики onClick
с именами handleArrClick
и handleObjClick
для установки состояний массива и объекта соответственно. Когда срабатывает handleArrClick
, мы вызываем setArr
и используем оператор распространения ES6 для распространения уже существующих значений массива и добавления к нему newArr
.
Мы сделали то же самое для обработчика handleObjClick
. Здесь мы вызвали setObj
, распространили существующие значения объекта с помощью оператора распространения ES6 и обновили значения name
и age
.
useState
Как мы уже видели, мы устанавливаем состояния с помощью useState
, передавая новое значение функции обновления. Если средство обновления вызывается несколько раз, новые значения будут добавлены в очередь, и повторная визуализация будет выполнена соответствующим образом с использованием сравнения JavaScript Object.is
.
Состояния обновляются асинхронно. Это означает, что новое состояние сначала добавляется к ожидающему состоянию, а затем состояние обновляется. Таким образом, вы все равно можете получить старое значение состояния, если сразу же получите доступ к состоянию, которое оно установило.
Давайте рассмотрим следующий пример, чтобы наблюдать за этим поведением.
В приведенном выше коде мы создали состояние count
с помощью хука useState
. Затем мы создали обработчик onClick
для увеличения состояния count
при каждом нажатии кнопки. Обратите внимание, что, несмотря на то, что состояние count
увеличилось, как показано в теге h2
, предыдущее состояние по-прежнему регистрируется в консоли. Это связано с асинхронной природой хука.
Если мы хотим получить новое состояние, мы можем обрабатывать его так же, как и асинхронные функции. Вот один из способов сделать это.
Здесь мы сохранили созданный newCountValue
для хранения обновленного значения счетчика, а затем установили состояние count
с обновленным значением. Затем мы записали обновленное значение счетчика в консоль.
Хук useEffect
useEffect
— еще один важный хук React, используемый в большинстве проектов. Он делает то же самое, что и методы жизненного цикла componentDidMount
, componentWillUnmount
и componentDidUpdate
на основе класса. useEffect
дает нам возможность писать императивный код, который может иметь побочные эффекты в приложении. Примеры таких эффектов включают ведение журнала, подписки, мутации и т. д.
Пользователь может решить, когда использовать useEffect
, однако, если он не установлен, побочные эффекты будут выполняться при каждом рендеринге или повторном рендеринге.
Рассмотрим пример ниже.
import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ console.log(count) }) return( <div> ... </div> ) }
В приведенном выше коде мы просто записали count
в useEffect
. Это будет выполняться после каждого рендеринга компонента.
Иногда нам может понадобиться запустить хук один раз (на монтировании) в нашем компоненте. Мы можем добиться этого, предоставив второй параметр useEffect
.
import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ setCount(count + 1) }, []) return( <div> <h1>{count}</h1> ... </div> ) }
useEffect
имеет два параметра, первый параметр — это функция, которую мы хотим запустить, а второй параметр — это массив зависимостей. Если второй параметр не указан, хук будет работать непрерывно.
Передавая пустую квадратную скобку второму параметру хука, мы указываем React запускать хук useEffect
только один раз на монтировании. Это отобразит значение 1
в теге h1
, потому что счетчик будет обновлен один раз, с 0 на 1, при монтировании компонента.
Мы также могли бы запускать наш побочный эффект всякий раз, когда изменяются некоторые зависимые значения. Это можно сделать, передав эти значения в списке зависимостей.
Например, мы могли бы заставить useEffect
запускаться всякий раз, когда изменяется count
, как показано ниже.
import { useState, useEffect } from "react"; const App = () => { const [count, setCount] = useState(0); useEffect(() => { console.log(count); }, [count]); return ( <div> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; export default App;
Приведенный выше useEffect
запустится при выполнении любого из этих двух условий.
- При монтировании — после рендеринга компонента.
- При изменении значения
count
.
При монтировании выражение console.log
будет запущено, а count
журнала будет равен 0. После обновления count
выполняется второе условие, поэтому useEffect
запускается снова, это будет продолжаться всякий раз, когда нажимается кнопка.
Как только мы передаем второй аргумент useEffect
, ожидается, что мы передадим ему все зависимости. Если у вас установлен ESLINT
, он покажет ошибку lint, если какая-либо зависимость не будет передана в список параметров. Это также может привести к неожиданному поведению побочного эффекта, особенно если он зависит от непереданных параметров.
Очистка эффекта
useEffect
также позволяет очищать ресурсы перед размонтированием компонента. Это может быть необходимо для предотвращения утечек памяти и повышения эффективности приложения. Для этого мы бы вернули функцию очистки в конце хука.
useEffect(() => { console.log('mounted') return () => console.log('unmounting... clean up here') })
Приведенный выше хук useEffect
будет mounted
в журнале, когда компонент будет смонтирован. Размонтирование… очистка здесь будет регистрироваться при размонтировании компонента. Это может произойти, когда компонент удален из пользовательского интерфейса.
Процесс очистки обычно следует приведенной ниже форме.
useEffect(() => { //The effect we intend to make effect //We then return the clean up return () => the cleanup/unsubscription })
Хотя вы можете не найти так много вариантов использования подписок useEffect
, это полезно при работе с подписками и таймерами. В частности, при работе с веб-сокетами может потребоваться отказаться от подписки на сеть, чтобы сэкономить ресурсы и повысить производительность при размонтировании компонента.
Получение и повторная выборка данных с помощью useEffect
Одним из самых распространенных вариантов использования хука useEffect
является выборка и предварительная выборка данных из API.
Чтобы проиллюстрировать это, мы будем использовать поддельные пользовательские данные, которые я создал из JSONPlaceholder
, для извлечения данных с помощью хука useEffect
.
import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [users, setUsers] = useState([]); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/users"; useEffect(() => { const fetchUsers = async () => { const { data } = await axios.get(endPoint); setUsers(data); }; fetchUsers(); }, []); return ( <div className="App"> {users.map((user) => ( <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> ))} </div> ); }
В приведенном выше коде мы создали состояние users
с помощью хука useState
. Затем мы получили данные из API с помощью Axios. Это асинхронный процесс, поэтому мы использовали функцию async/await, мы могли бы также использовать точку, а затем синтаксис. Поскольку мы получили список пользователей, мы просто сопоставили его для отображения данных.
Обратите внимание, что мы передали хуку пустой параметр. Это гарантирует, что он вызывается только один раз при монтировании компонента.
Мы также можем обновить данные при изменении некоторых условий. Мы покажем это в коде ниже.
import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [userIDs, setUserIDs] = useState([]); const [user, setUser] = useState({}); const [currentID, setCurrentID] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/userdata/users"; useEffect(() => { axios.get(endPoint).then(({ data }) => setUserIDs(data)); }, []); useEffect(() => { const fetchUserIDs = async () => { const { data } = await axios.get(`${endPoint}/${currentID}`}); setUser(data); }; fetchUserIDs(); }, [currentID]); const moveToNextUser = () => { setCurrentID((prevId) => (prevId < userIDs.length ? prevId + 1 : prevId)); }; const moveToPrevUser = () => { setCurrentID((prevId) => (prevId === 1 ? prevId : prevId - 1)); }; return ( <div className="App"> <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> <button onClick={moveToPrevUser}>Prev</button> <button onClick={moveToNextUser}>Next</button> </div> ); }
Здесь мы создали два useEffect
. В первом мы использовали синтаксис точки, затем, чтобы получить всех пользователей из нашего API. Это необходимо для определения количества пользователей.
Затем мы создали еще один хук useEffect
, чтобы получить пользователя на основе id
. Этот useEffect
будет обновлять данные при каждом изменении идентификатора. Чтобы гарантировать это, мы передали id
в списке зависимостей.
Затем мы создали функции для обновления значения нашего id
при каждом нажатии кнопки. Как только значение id
изменится, useEffect
запустится снова и обновит данные.
Если мы хотим, мы можем даже очистить или отменить токен на основе обещаний в Axios, мы могли бы сделать это с помощью метода очистки, описанного выше.
useEffect(() => { const source = axios.CancelToken.source(); const fetchUsers = async () => { const { data } = await axios.get(`${endPoint}/${num}`, { cancelToken: source.token }); setUser(data); }; fetchUsers(); return () => source.cancel(); }, [num]);
Здесь мы передали токен Axios в качестве второго параметра в axios.get
. Когда компонент размонтируется, мы отменяем подписку, вызывая метод отмены исходного объекта.
Хук useReducer
useReducer
— очень полезный хук React, который делает то же самое, что и хук useState
. Согласно документации React, этот хук следует использовать для обработки более сложной логики, чем хук useState
. Стоит отметить, что хук useState
внутренне реализован с помощью хука useReducer.
Хук принимает редюсер в качестве аргумента и может дополнительно принимать начальное состояние и функцию инициализации в качестве аргументов.
const [state, dispatch] = useReducer(reducer, initialState, init)
Здесь init
— это функция, и она используется всякий раз, когда мы хотим лениво создать начальное состояние.
Давайте посмотрим, как реализовать хук useReducer
, создав простое приложение, как показано в песочнице ниже.
Во-первых, мы должны создать наш редьюсер для хранения состояний.
export const ADD_TODO = "ADD_TODO"; export const REMOVE_TODO = "REMOVE_TODO"; export const COMPLETE_TODO = "COMPLETE_TODO"; const reducer = (state, action) => { switch (action.type) { case ADD_TODO: const newTodo = { id: action.id, text: action.text, completed: false }; return [...state, newTodo]; case REMOVE_TODO: return state.filter((todo) => todo.id !== action.id); case COMPLETE_TODO: const completeTodo = state.map((todo) => { if (todo.id === action.id) { return { ...todo, completed: !todo.completed }; } else { return todo; } }); return completeTodo; default: return state; } }; export default reducer;
Мы создали три константы, соответствующие нашим типам действий. Мы могли бы использовать строки напрямую, но этот метод предпочтительнее, чтобы избежать опечаток.
Затем мы создали нашу функцию-редуктор. Как и в Redux
, редюсер должен принимать состояние и объект действия. Но в отличие от Redux здесь нам не нужно инициализировать наш редюсер.
Кроме того, во многих случаях использования управления состоянием useReducer
вместе с dispatch
, предоставляемой через контекст, может позволить более крупному приложению запускать действия, обновлять state
и прослушивать его.
Затем мы использовали операторы switch
для проверки типа действия, переданного пользователем. Если тип действия — ADD_TODO
, мы хотим передать новую задачу, а если это REMOVE_TODO
, мы хотим отфильтровать задачи и удалить ту, которая соответствует id
, переданному пользователем. Если это COMPLETE_TODO
, мы хотим сопоставить задачи и переключить ту, с id
, переданным пользователем.
Вот файл App.js
, в котором мы реализовали reducer
.
import { useReducer, useState } from "react"; import "./styles.css"; import reducer, { ADD_TODO, REMOVE_TODO, COMPLETE_TODO } from "./reducer"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = [ { id: id, text: "First Item", completed: false } ]; //We could also pass an empty array as the initial state //const initialState = [] const [state, dispatch] = useReducer(reducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); dispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="App"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit">+</button> </form> <div className="todos"> {state.map((todo) => ( <div key={todo.id} className="todoItem"> <p className={todo.completed && "strikethrough"}>{todo.text}</p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ))} </div> </div> ); }
Здесь мы создали форму, содержащую элемент ввода для сбора пользовательского ввода и кнопку для запуска действия. Когда форма отправляется, мы отправляем действие типа ADD_TODO
, передавая новый идентификатор и текст задачи. Мы создали новый идентификатор, увеличив значение предыдущего идентификатора на 1. Затем мы очистили текстовое поле ввода. Чтобы удалить и завершить задачу, мы просто отправили соответствующие действия. Они уже реализованы в редюсере, как показано выше.
Однако волшебство происходит, потому что мы используем хук useReducer
. Этот хук принимает редюсер и начальное состояние и возвращает состояние и функцию отправки. Здесь функция диспетчеризации служит той же цели, что и функция установки для хука useState
, и мы можем называть ее как угодно вместо dispatch
.
Чтобы отобразить элементы списка дел, мы просто сопоставили список дел, возвращаемых в нашем объекте состояния, как показано в приведенном выше коде.
Это показывает силу хука useReducer
. Мы также могли бы добиться этой функциональности с помощью хука useState
, но, как видно из приведенного выше примера, хук useReducer
помог нам сделать все более аккуратно. useReducer
часто полезен, когда объект состояния представляет собой сложную структуру и обновляется по-разному, в отличие от простой замены значения. Кроме того, как только эти функции обновления становятся более сложными, useReducer
упрощает сохранение всей этой сложности в функции-редюсере (которая является чистой функцией JS), что упрощает написание тестов только для функции-редуктора.
Мы также могли бы передать третий аргумент useReducer
, чтобы лениво создать начальное состояние. Это означает, что мы можем вычислить начальное состояние в функции init
.
Например, мы могли бы создать функцию init
следующим образом:
const initFunc = () => [ { id: id, text: "First Item", completed: false } ]
а затем передать его в наш хук useReducer
.
const [state, dispatch] = useReducer(reducer, initialState, initFunc)
Если мы это сделаем, initFunc
переопределит предоставленное нами initialState
, и начальное состояние будет вычисляться лениво.
Хук useContext
React Context API предоставляет способ обмена состояниями или данными в дереве компонентов React. API был доступен в React в качестве экспериментальной функции некоторое время, но его стало безопасно использовать в React 16.3.0. API упрощает обмен данными между компонентами, устраняя необходимость сверления опор.
Хотя вы можете применить React Context ко всему приложению, его также можно применить к части приложения.
Чтобы использовать хук, вам нужно сначала создать контекст с помощью React.createContext
а затем этот контекст можно передать в хук.
Чтобы продемонстрировать использование хука useContext
, давайте создадим простое приложение, которое будет увеличивать размер шрифта во всем нашем приложении.
Давайте создадим наш контекст в файле context.js
.
import { createContext } from "react"; //Here, we set the initial fontSize as 16. const fontSizeContext = createContext(16); export default fontSizeContext;
Здесь мы создали контекст и передали ему начальное значение 16
, а затем экспортировали контекст. Далее давайте подключим наш контекст к нашему приложению.
import FontSizeContext from "./context"; import { useState } from "react"; import PageOne from "./PageOne"; import PageTwo from "./PageTwo"; const App = () => { const [size, setSize] = useState(16); return ( <FontSizeContext.Provider value={size}> <PageOne /> <PageTwo /> <button onClick={() => setSize(size + 5)}>Increase font</button> <button onClick={() => setSize((prevSize) => Math.min(11, prevSize - 5)) } > Decrease font </button> </FontSizeContext.Provider> ); }; export default App;
В приведенном выше коде мы обернули все наше дерево компонентов с помощью FontSizeContext.Provider
и передали size
его значению. Здесь size
— это состояние, созданное с помощью хука useState
. Это позволяет нам изменять свойство value всякий раз, когда изменяется состояние size
. Обернув весь компонент Provider
, мы можем получить доступ к контексту в любом месте нашего приложения.
Например, мы получили доступ к контексту в <PageOne />
и <PageTwo />
. В результате размер шрифта в этих двух компонентах увеличится, когда мы увеличим его из файла App.js
Мы можем увеличить или уменьшить размер шрифта с помощью кнопок, как показано выше, и как только мы это сделаем, размер шрифта изменится во всем приложении.
import { useContext } from "react"; import context from "./context"; const PageOne = () => { const size = useContext(context); return <p style={{ fontSize: `${size}px` }}>Content from the first page</p>; }; export default PageOne;
Здесь мы получили доступ к контексту с помощью хука useContext
из нашего компонента PageOne
. Затем мы использовали этот контекст для установки нашего свойства размера шрифта. Аналогичная процедура применяется к файлу PageTwo.js
.
Темы или другие конфигурации уровня приложения более высокого уровня являются хорошими кандидатами на роль контекстов.
Использование useContext
и useReducer
При использовании с хуком useReducer
useContext
позволяет нам создать собственную систему управления состоянием. Мы можем создавать глобальные состояния и легко управлять ими в нашем приложении.
Давайте улучшим наше приложение списков дел, используя контекстный API.
Как обычно, нам нужно создать todoContext
в файле todoContext.js
.
import { createContext } from "react"; const initialState = []; export default createContext(initialState);
Здесь мы создали контекст, передав начальное значение пустого массива. Затем мы экспортировали контекст.
Давайте реорганизуем наш файл App.js
, разделив список дел и элементы.
import { useReducer, useState } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import TodoList from "./TodoList"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <div className="app"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </TodoContext.Provider> ); }
Здесь мы обернули наш файл App.js
с помощью TodoContext.Provider
, а затем передали ему возвращаемые значения нашего todoReducer
. Это делает состояние редуктора и функцию dispatch
доступными для всего нашего приложения.
Затем мы разделили отображение списка дел на компонент TodoList
. Мы сделали это без сверления опор благодаря Context API. Давайте посмотрим на файл TodoList.js
.
import React, { useContext } from "react"; import TodoContext from "./todoContext"; import Todo from "./Todo"; const TodoList = () => { const [state] = useContext(TodoContext); return ( <div className="todos"> {state.map((todo) => ( <Todo key={todo.id} todo={todo} /> ))} </div> ); }; export default TodoList;
Используя деструктуризацию массива, мы можем получить доступ к состоянию (выйдя из функции диспетчеризации) из контекста, используя хук useContext
. Затем мы можем отобразить состояние и отобразить элементы списка дел. Мы все еще извлекли это в компонент Todo
. Функция карты ES6+ требует от нас передачи уникального ключа, и, поскольку нам нужна конкретная задача, мы также передаем ее.
Давайте взглянем на компонент Todo
.
import React, { useContext } from "react"; import TodoContext from "./todoContext"; import { REMOVE_TODO, COMPLETE_TODO } from "./todoReducer"; const Todo = ({ todo }) => { const [, dispatch] = useContext(TodoContext); const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="todoItem"> <p className={todo.completed ? "strikethrough" : "nostrikes"}> {todo.text} </p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ); }; export default Todo;
Снова используя деструктуризацию массива, мы получили доступ к функции диспетчеризации из контекста. Это позволяет нам определить функции completeTodo
и removeTodo
, как уже обсуждалось в разделе useReducer
. С реквизитом todo
, переданным из todoList.js
, мы можем отобразить элемент списка дел. Мы также можем пометить его как выполненное и удалить задачу по своему усмотрению.
Также возможно вложить более одного провайдера контекста в корень нашего приложения. Это означает, что мы можем использовать более одного контекста для выполнения различных функций в приложении.
Чтобы продемонстрировать это, давайте добавим тему в пример списка дел.
Вот что мы будем строить.
Опять же, нам нужно создать themeContext
. Для этого создайте файл themeContext.js
и добавьте следующие коды.
import { createContext } from "react"; import colors from "./colors"; export default createContext(colors.light);
Здесь мы создали контекст и передали colors.light
в качестве начального значения. Давайте определим цвета с помощью этого свойства в файле colors.js
.
const colors = { light: { backgroundColor: "#fff", color: "#000" }, dark: { backgroundColor: "#000", color: "#fff" } }; export default colors;
В приведенном выше коде мы создали объект colors
, содержащий свойства light и dark. Каждое свойство имеет объект backgroundColor
и color
.
Затем мы создаем themeReducer
для обработки состояний темы.
import Colors from "./colors"; export const LIGHT = "LIGHT"; export const DARK = "DARK"; const themeReducer = (state, action) => { switch (action.type) { case LIGHT: return { ...Colors.light }; case DARK: return { ...Colors.dark }; default: return state; } }; export default themeReducer;
Как и все редюсеры, themeReducer
принимает состояние и действие. Затем он использует оператор switch
для определения текущего действия. Если он имеет тип LIGHT
, мы просто назначаем Colors.light
, а если тип DARK
, мы отображаем Colors.dark
. Мы могли бы легко сделать это с помощью хука useState
, но мы выбираем useReducer
, чтобы довести мысль до конца.
themeReducer
, мы можем затем интегрировать его в наш файл App.js
import { useReducer, useState, useCallback } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import ThemeContext from "./themeContext"; import TodoList from "./TodoList"; import themeReducer, { DARK, LIGHT } from "./themeReducer"; import Colors from "./colors"; import ThemeToggler from "./ThemeToggler"; const themeSetter = useCallback( theme => themeDispatch({type: theme}, [themeDispatch]); export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const [themeState, themeDispatch] = useReducer(themeReducer, Colors.light); const themeSetter = useCallback( (theme) => { themeDispatch({ type: theme }); }, [themeDispatch] ); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <ThemeContext.Provider value={[ themeState, themeSetter ]} > <div className="app" style={{ ...themeState }}> <ThemeToggler /> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </ThemeContext.Provider> </TodoContext.Provider> ); }
В приведенном выше коде мы добавили несколько вещей в наше уже существующее приложение списка дел. Мы начали с импорта ThemeContext
, themeReducer
, ThemeToggler
и Colors
. We created a reducer using the useReducer
hook, passing the themeReducer
and an initial value of Colors.light
to it. This returned the themeState
and themeDispatch
to us.
We then nested our component with the provider function from the ThemeContext
, passing the themeState
and the dispatch
functions to it. We also added theme styles to it by spreading out the themeStates
. This works because the colors
object already defined properties similar to what the JSX styles will accept.
However, the actual theme toggling happens in the ThemeToggler
component. Давайте посмотрим на это.
import ThemeContext from "./themeContext"; import { useContext, useState } from "react"; import { DARK, LIGHT } from "./themeReducer"; const ThemeToggler = () => { const [showLight, setShowLight] = useState(true); const [themeState, themeSetter] = useContext(ThemeContext); const dispatchDarkTheme = () => themeSetter(DARK); const dispatchLightTheme = () => themeSetter(LIGHT); const toggleTheme = () => { showLight ? dispatchDarkTheme() : dispatchLightTheme(); setShowLight(!showLight); }; console.log(themeState); return ( <div> <button onClick={toggleTheme}> {showLight ? "Change to Dark Theme" : "Change to Light Theme"} </button> </div> ); }; export default ThemeToggler;
In this component, we used the useContext
hook to retrieve the values we passed to the ThemeContext.Provider
from our App.js
file. As shown above, these values include the ThemeState
, dispatch function for the light theme, and dispatch function for the dark theme. Thereafter, we simply called the dispatch functions to toggle the themes. We also created a state showLight
to determine the current theme. This allows us to easily change the button text depending on the current theme.
The useMemo
Hook
The useMemo
hook is designed to memoize expensive computations. Memoization simply means caching. It caches the computation result with respect to the dependency values so that when the same values are passed, useMemo
will just spit out the already computed value without recomputing it again. This can significantly improve performance when done correctly.
The hook can be used as follows:
const memoizedResult = useMemo(() => expensiveComputation(a, b), [a, b])
Let's consider three cases of the useMemo
hook.
- When the dependency values, a and b remain the same.
TheuseMemo
hook will return the already computed memoized value without recomputation. - When the dependency values, a and b change.
The hook will recompute the value. - When no dependency value is passed.
The hook will recompute the value.
Let's take a look at an example to demonstrate this concept.
In the example below, we'll be computing the PAYE and Income after PAYE of a company's employees with fake data from JSONPlaceholder.
The calculation will be based on the personal income tax calculation procedure for Nigeria providers by PricewaterhouseCoopers available here.
This is shown in the sandbox below.
First, we queried the API to get the employees' data. We also get data for each employee (with respect to their employee id).
const [employee, setEmployee] = useState({}); const [employees, setEmployees] = useState([]); const [num, setNum] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; useEffect(() => { const getEmployee = async () => { const { data } = await axios.get(`${endPoint}/${num}`); setEmployee(data); }; getEmployee(); }, [num]); useEffect(() => { axios.get(endPoint).then(({ data }) => setEmployees(data)); }, [num]);
Мы использовали axios
и метод async/await
в первом useEffect
а затем синтаксис точки во втором. Эти два подхода работают одинаково.
Далее, используя данные о сотрудниках, которые мы получили сверху, рассчитаем переменные рельефа:
const taxVariablesCompute = useMemo(() => { const { income, noOfChildren, noOfDependentRelatives } = employee; //supposedly complex calculation //tax relief computations for relief Allowance, children relief, // relatives relief and pension relief const reliefs = reliefAllowance1 + reliefAllowance2 + childrenRelief + relativesRelief + pensionRelief; return reliefs; }, [employee]);
Это довольно сложный расчет, поэтому нам пришлось обернуть его в хук useMemo
, чтобы запомнить или оптимизировать. Запоминание таким образом гарантирует, что расчет не будет пересчитан, если мы снова попытаемся получить доступ к тому же сотруднику.
Кроме того, используя значения налоговых льгот, полученные выше, мы хотели бы рассчитать PAYE и доход после PAYE.
const taxCalculation = useMemo(() => { const { income } = employee; let taxableIncome = income - taxVariablesCompute; let PAYE = 0; //supposedly complex calculation //computation to compute the PAYE based on the taxable income and tax endpoints const netIncome = income - PAYE; return { PAYE, netIncome }; }, [employee, taxVariablesCompute]);
Мы выполнили расчет налогов (довольно сложный расчет), используя вычисленные выше налоговые переменные, а затем запомнили их с помощью хука useMemo
.
Полный код доступен здесь.
Это следует из процедуры расчета налога, приведенной здесь. Сначала мы рассчитали налоговые льготы с учетом дохода, количества детей и числа родственников-иждивенцев. Затем мы пошагово умножили налогооблагаемый доход на ставки НДФЛ. Хотя рассматриваемый расчет не совсем необходим для этого руководства, он предназначен для того, чтобы показать нам, почему useMemo
может быть необходимо. Это также довольно сложное вычисление, поэтому нам может понадобиться запомнить его с помощью useMemo
, как показано выше.
После расчета значений мы просто отображали результат.
Обратите внимание на следующее о useMemo
.
-
useMemo
следует использовать только тогда, когда необходимо оптимизировать вычисления. Другими словами, когда пересчет стоит дорого. - Рекомендуется сначала записать расчет без запоминания и запоминать его только в том случае, если он вызывает проблемы с производительностью.
- Ненужное и неуместное использование хука
useMemo
может даже усугубить проблемы с производительностью. - Иногда чрезмерное запоминание также может вызвать проблемы с производительностью.
Хук useCallback
useCallback
служит той же цели, что и useMemo
, но возвращает запомненный обратный вызов вместо запомненного значения. Другими словами, useCallback
аналогичен передаче useMemo
без вызова функции.
Например, рассмотрите следующие коды ниже.
import React, {useCallback, useMemo} from 'react' const MemoizationExample = () => { const a = 5 const b = 7 const memoResult = useMemo(() => a + b, [a, b]) const callbackResult = useCallback(a + b, [a, b]) console.log(memoResult) console.log(callbackResult) return( <div> ... </div> ) } export default MemoizationExample
В приведенном выше примере и memoResult
, и callbackResult
дадут одно и то же значение 12
. Здесь useCallback
вернет запомненное значение. Однако мы также можем заставить его возвращать запомненный обратный вызов, передав его как функцию.
Приведенный ниже useCallback
вернет запомненный обратный вызов.
... const callbackResult = useCallback(() => a + b, [a, b]) ...
Затем мы можем инициировать обратный вызов при выполнении действия или в useEffect
.
import {useCallback, useEffect} from 'react' const memoizationExample = () => { const a = 5 const b = 7 const callbackResult = useCallback(() => a + b, [a, b]) useEffect(() => { const callback = callbackResult() console.log(callback) }) return ( <div> <button onClick= {() => console.log(callbackResult())}> Trigger Callback </button> </div> ) } export default memoizationExample
В приведенном выше коде мы определили функцию обратного вызова, используя хук useCallback
. Затем мы вызывали обратный вызов в useEffect
при монтировании компонента, а также при нажатии кнопки.
И useEffect
, и нажатие кнопки дают одинаковый результат.
Обратите внимание, что понятия, что можно и чего нельзя делать, которые применяются к useMemo
, также применимы к useCallback
. Мы можем воссоздать пример useMemo
с помощью useCallback
.
Хук useRef
useRef
возвращает объект, который может сохраняться в приложении. Хук имеет только одно свойство, current
, и мы можем легко передать ему аргумент.
Он служит той же цели, что и createRef
, используемый в компонентах на основе классов. Мы можем создать ссылку с этим хуком следующим образом:
const newRef = useRef('')
Здесь мы создали новую ссылку с именем newRef
и передали ей пустую строку.
Этот крючок используется в основном для двух целей:
- Доступ или манипулирование DOM, и
- Сохранение изменяемых состояний — это полезно, когда мы не хотим, чтобы компонент перерисовывался при изменении значения.
Манипуляции с DOM
При передаче элементу DOM объект ref указывает на этот элемент и может использоваться для доступа к его атрибутам и свойствам DOM.
Вот очень простой пример, демонстрирующий эту концепцию.
import React, {useRef, useEffect} from 'react' const RefExample = () => { const headingRef = useRef('') console.log(headingRef) return( <div> <h1 className='topheading' ref={headingRef}>This is a h1 element</h1> </div> ) } export default RefExample
В приведенном выше примере мы определили headingRef
с помощью хука useRef
, передающего пустую строку. Затем мы устанавливаем ссылку в теге h1
, передавая ref = {headingRef}
. Установив эту ссылку, мы попросили headingRef
указать на наш элемент h1
. Это означает, что мы можем получить доступ к свойствам нашего элемента h1
из ref.
Чтобы убедиться в этом, если мы проверим значение console.log(headingRef)
, мы получим {current: HTMLHeadingElement}
или {current: h1}
и сможем оценить все свойства или атрибуты элемента. То же самое относится и к любому другому элементу HTML.
Например, мы могли бы выделить текст курсивом при монтировании компонента.
useEffect(() => { headingRef.current.style.font; }, []);
Мы даже можем изменить текст на что-то другое.
... headingRef.current.innerHTML = "A Changed H1 Element"; ...
Мы даже можем изменить цвет фона родительского контейнера.
... headingRef.current.parentNode.style.backgroundColor = "red"; ...
Здесь можно выполнять любые манипуляции с DOM. Обратите внимание, что headingRef.current
можно читать так же, как document.querySelector('.topheading')
.
Один интересный вариант использования хука useRef
при манипулировании элементом DOM — сфокусировать курсор на элементе ввода. Давайте быстро пробежимся по нему.
import {useRef, useEffect} from 'react' const inputRefExample = () => { const inputRef = useRef(null) useEffect(() => { inputRef.current.focus() }, []) return( <div> <input ref={inputRef} /> <button onClick = {() => inputRef.current.focus()}>Focus on Input </button> </div> ) } export default inputRefExample
В приведенном выше коде мы создали inputRef
с помощью хука useRef
, а затем попросили его указать на элемент ввода. Затем мы заставили курсор сфокусироваться на входной ссылке при загрузке компонента и при нажатии кнопки с помощью inputRef.current.focus()
. Это возможно, потому что focus()
является атрибутом элементов ввода, и поэтому ссылка сможет оценить методы.
Ссылки, созданные в родительском компоненте, можно оценить в дочернем компоненте, перенаправив его с помощью React.forwardRef()
. Давайте посмотрим на это.
Давайте сначала создадим еще один компонент NewInput.js
и добавим в него следующие коды.
import { useRef, forwardRef } from "react"; const NewInput = forwardRef((props, ref) => { return <input placeholder={props.val} ref={ref} />; }); export default NewInput;
Этот компонент принимает props
и ref
. Мы передали ref в его опору ref и props.val
в опору-заполнитель. Обычные компоненты React не имеют атрибута ref
. Этот атрибут доступен только тогда, когда мы обертываем его React.forwardRef
, как показано выше.
Затем мы можем легко вызвать это в родительском компоненте.
... <NewInput val="Just an example" ref={inputRef} /> ...
Сохранение изменяемых состояний
Рефы используются не только для управления элементами DOM, их также можно использовать для хранения изменяемых значений без повторного рендеринга всего компонента.
В следующем примере определяется количество нажатий кнопки без повторного рендеринга компонента.
import { useRef } from "react"; export default function App() { const countRef = useRef(0); const increment = () => { countRef.current++; console.log(countRef); }; return ( <div className="App"> <button onClick={increment}>Increment </button> </div> ); }
В приведенном выше коде мы увеличивали countRef
при нажатии кнопки, а затем записывали его в консоль. Хотя значение увеличивается, как показано в консоли, мы не сможем увидеть никаких изменений, если попытаемся оценить его непосредственно в нашем компоненте. Он будет обновляться только в компоненте при повторном рендеринге.
Обратите внимание, что хотя useState
является асинхронным, useRef
является синхронным. Другими словами, значение доступно сразу после его обновления.
Хук useLayoutEffect
Как и хук useEffect
, useLayoutEffect
вызывается после монтирования и рендеринга компонента. Этот хук срабатывает после мутации DOM и делает это синхронно. Помимо синхронного вызова после мутации DOM, useLayoutEffect
делает то же самое, что и useEffect
.
useLayoutEffect
следует использовать только для выполнения мутации DOM или измерения, связанного с DOM, в противном случае вы должны использовать хук useEffect
. Использование хука useEffect
для функций изменения DOM может вызвать некоторые проблемы с производительностью, такие как мерцание, но useLayoutEffect
прекрасно справляется с ними, поскольку он запускается после того, как произошли изменения.
Давайте рассмотрим несколько примеров, чтобы продемонстрировать эту концепцию.
- Мы будем получать ширину и высоту окна при изменении размера.
import {useState, useLayoutEffect} from 'react' const ResizeExample = () =>{ const [windowSize, setWindowSize] = useState({width: 0, height: 0}) useLayoutEffect(() => { const resizeWindow = () => setWindowSize({ width: window.innerWidth, height: window.innerHeight }) window.addEventListener('resize', resizeWindow) return () => window.removeEventListener('resize', resizeWindow) }, []) return ( <div> <p>width: {windowSize.width}</p> <p>height: {windowSize.height}</p> </div> ) } export default ResizeExample
В приведенном выше коде мы создали состояние windowSize
со свойствами ширины и высоты. Затем мы устанавливаем состояние на ширину и высоту текущего окна соответственно при изменении размера окна. Мы также подчистили код при размонтировании. Процесс очистки необходим в useLayoutEffect
для очистки манипуляций с DOM и повышения эффективности.
- Давайте размоем текст с помощью
useLayoutEffect
.
import { useRef, useState, useLayoutEffect } from "react"; export default function App() { const paragraphRef = useRef(""); useLayoutEffect(() => { const { current } = paragraphRef; const blurredEffect = () => { current.style.color = "transparent"; current.style.textShadow = "0 0 5px rgba(0,0,0,0.5)"; }; current.addEventListener("click", blurredEffect); return () => current.removeEventListener("click", blurredEffect); }, []); return ( <div className="App"> <p ref={paragraphRef}>This is the text to blur</p> </div> ); }
Мы использовали useRef
и useLayoutEffect
вместе в приведенном выше коде. Сначала мы создали ссылку, paragraphRef
, чтобы указать на наш абзац. Затем мы создали прослушиватель событий по щелчку для отслеживания щелчка по абзацу, а затем размыли его, используя определенные нами свойства стиля. Наконец, мы очистили прослушиватель событий с помощью removeEventListener
.
useDispatch
и useSelector
useDispatch
— это хук Redux для диспетчеризации (запуска) действий в приложении. Он принимает объект действия в качестве аргумента и вызывает действие. useDispatch
— эквивалентность хука для mapDispatchToProps
.
С другой стороны, useSelector
— это хук Redux для оценки состояний Redux. Требуется функция для выбора точного редуктора Redux из хранилища, а затем возвращает соответствующие состояния.
Как только наш магазин Redux подключен к приложению React через поставщика Redux, мы можем вызывать действия с помощью useDispatch
и получать доступ к состояниям с помощью useSelector
. Каждое действие и состояние Redux можно оценить с помощью этих двух хуков.
Обратите внимание, что эти состояния поставляются с React Redux (пакетом, упрощающим оценку хранилища Redux в приложении React). Они недоступны в основной библиотеке Redux.
Эти крючки очень просты в использовании. Сначала мы должны объявить функцию отправки, а затем запустить ее.
import {useDispatch, useSelector} from 'react-redux' import {useEffect} from 'react' const myaction from '...' const ReduxHooksExample = () =>{ const dispatch = useDispatch() useEffect(() => { dispatch(myaction()); //alternatively, we can do this dispatch({type: 'MY_ACTION_TYPE'}) }, []) const mystate = useSelector(state => state.myReducerstate) return( ... ) } export default ReduxHooksExample
В приведенном выше коде мы импортировали useDispatch
и useSelector
из react react-redux
. Затем в useEffect
мы отправили действие. Мы могли бы определить действие в другом файле, а затем вызвать его здесь, или мы могли бы определить его напрямую, как показано в вызове useEffect
.
Как только мы отправим действия, наши состояния станут доступны. Затем мы можем получить состояние с помощью хука useSelector
, как показано ниже. Состояния можно использовать так же, как мы использовали бы состояния из хука useState
.
Давайте рассмотрим пример, демонстрирующий эти два хука.
Чтобы продемонстрировать эту концепцию, нам нужно создать хранилище Redux, редьюсер и действия. Чтобы все упростить, мы будем использовать библиотеку Redux Toolkit с нашей поддельной базой данных из JSONPlaceholder.
Для начала нам нужно установить следующие пакеты. Запустите следующие команды bash.
npm i redux @reduxjs/toolkit react-redux axios
Во-первых, давайте создадим employeesSlice.js
для обработки редьюсера и действия для API наших сотрудников.
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import axios from "axios"; const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; export const fetchEmployees = createAsyncThunk("employees/fetchAll", async () => { const { data } = await axios.get(endPoint); return data; }); const employeesSlice = createSlice({ name: "employees", initialState: { employees: [], loading: false, error: "" }, reducers: {}, extraReducers: { [fetchEmployees.pending]: (state, action) => { state.status = "loading"; }, [fetchEmployees.fulfilled]: (state, action) => { state.status = "success"; state.employees = action.payload; }, [fetchEmployees.rejected]: (state, action) => { state.status = "error"; state.error = action.error.message; } } }); export default employeesSlice.reducer;
Это стандартная настройка для набора инструментов Redux. Мы использовали createAsyncThunk
для доступа к промежуточному программному обеспечению Thunk
для выполнения асинхронных действий. Это позволило нам получить список сотрудников из API. Затем мы создали employeesSlice
и вернули «загрузка», «ошибка» и данные о сотрудниках в зависимости от типов действий.
Инструментарий Redux также упрощает настройку магазина. Вот магазин.
import { configureStore } from "@reduxjs/toolkit"; import { combineReducers } from "redux"; import employeesReducer from "./employeesSlice"; const reducer = combineReducers({ employees: employeesReducer }); export default configureStore({ reducer });;
Здесь мы использовали combineReducers
для объединения редюсеров и функцию configureStore
, предоставляемую набором инструментов Redux, для настройки хранилища.
Давайте продолжим использовать это в нашем приложении.
Во-первых, нам нужно подключить Redux к нашему приложению React. В идеале это нужно делать в корне нашего приложения. Мне нравится делать это в файле index.js
.
import React, { StrictMode } from "react"; import ReactDOM from "react-dom"; import store from "./redux/store"; import { Provider } from "react-redux"; import App from "./App"; const rootElement = document.getElementById("root"); ReactDOM.render( <Provider store={store}> <StrictMode> <App /> </StrictMode> </Provider>, rootElement );
Здесь я импортировал магазин, который создал выше, а также Provider
из react-redux
.
Затем я обернул все приложение функцией Provider
, передав ей хранилище. Это делает магазин доступным для всего нашего приложения.
Затем мы можем перейти к использованию useDispatch
и useSelector
для получения данных.
Давайте сделаем это в нашем файле App.js
import { useDispatch, useSelector } from "react-redux"; import { fetchEmployees } from "./redux/employeesSlice"; import { useEffect } from "react"; export default function App() { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchEmployees()); }, [dispatch]); const employeesState = useSelector((state) => state.employees); const { employees, loading, error } = employeesState; return ( <div className="App"> {loading ? ( "Loading..." ) : error ? ( <div>{error}</div> ) : ( <> <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <h3>{`${employee.firstName} ${employee.lastName}`}</h3> </div> ))} </> )} </div> ); }
В приведенном выше коде мы использовали хук useDispatch
для вызова действия fetchEmployees
, созданного в файле employeesSlice.js
. Это делает состояние сотрудников доступным в нашем приложении. Затем мы использовали хук useSelector
для получения состояний. После этого мы отображали результаты, сопоставляя employees
.
useHistory
Навигация очень важна в приложении React. Хотя вы можете добиться этого несколькими способами, React Router предоставляет простой, эффективный и популярный способ динамической маршрутизации в приложении React. Кроме того, React Router предоставляет несколько хуков для оценки состояния маршрутизатора и выполнения навигации в браузере, но для их использования вам необходимо сначала правильно настроить приложение.
Чтобы использовать любой хук React Router, мы должны сначала обернуть наше приложение с помощью BrowserRouter
. Затем мы можем вложить маршруты с помощью Switch
и Route
.
Но сначала мы должны установить пакет, выполнив следующие команды.
npm install react-router-dom
Затем нам нужно настроить наше приложение следующим образом. Мне нравится делать это в моем файле App.js
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import Employees from "./components/Employees"; export default function App() { return ( <div className="App"> <Router> <Switch> <Route path='/'> <Employees /> </Route> ... </Switch> </Router> </div> ); }
У нас может быть столько маршрутов, сколько возможно, в зависимости от количества компонентов, которые мы хотим визуализировать. Здесь мы визуализировали только компонент Employees
. Атрибут path
сообщает React Router DOM путь компонента и может быть оценен с помощью строки запроса или различных других методов.
Здесь важен порядок. Корневой маршрут должен быть размещен ниже дочернего маршрута и так далее. Чтобы переопределить этот порядок, вам нужно включить exact
ключевое слово в корневой маршрут.
<Route path='/' exact > <Employees /> </Route>
Теперь, когда мы настроили маршрутизатор, мы можем использовать хук useHistory
и другие хуки React Router в нашем приложении.
Чтобы использовать хук useHistory
, нам нужно сначала объявить его следующим образом.
import {useHistory} from 'history' import {useHistory} from 'react-router-dom' const Employees = () =>{ const history = useHistory() ... }
Если мы запишем историю в консоль, мы увидим несколько связанных с ней свойств. К ним относятся block
, createHref
, go
, goBack
, goForward
, length
, listen
, location
, push
, replace
. Хотя все эти свойства полезны, вы, скорее всего, будете использовать history.push
и history.replace
чаще, чем другие свойства.
Давайте воспользуемся этим свойством для перехода с одной страницы на другую.
Предположим, мы хотим получить данные о конкретном сотруднике, когда нажимаем на его имена. Мы можем использовать хук useHistory
для перехода на новую страницу, где будет отображаться информация о сотруднике.
function moveToPage = (id) =>{ history.push(`/employees/${id}`) }
Мы можем реализовать это в нашем файле Employee.js
, добавив следующее.
import { useEffect } from "react"; import { Link, useHistory, useLocation } from "react-router-dom"; export default function Employees() { const history = useHistory(); function pushToPage = (id) => { history.push(`/employees/${id}`) } ... return ( <div> ... <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <span>{`${employee.firstName} ${employee.lastName} `}</span> <button onClick={pushToPage(employee.id)}> » </button> </div> ))} </div> ); }
В функции pushToPage
мы использовали history
из хука useHistory
для перехода на страницу сотрудника и передачи вместе с ним идентификатора сотрудника.
Хук useLocation
Этот хук также поставляется с React Router DOM. Это очень популярный хук, используемый для работы с параметром строки запроса. Этот хук похож на window.location
в браузере.
import {useLocation} from 'react' const LocationExample = () =>{ const location = useLocation() return ( ... ) } export default LocationExample
useLocation
возвращает имя pathname
, параметр search
, hash
и state
. Наиболее часто используемые параметры включают имя pathname
и search
, но вы также можете использовать hash
и state
многое в своем приложении.
Свойство location pathname
вернет путь, который мы установили в нашей настройке Route
. В то время как search
вернет параметр поиска запроса, если он есть. Например, если мы передаем 'http://mywebsite.com/employee/?id=1'
в наш запрос, имя pathname
будет /employee
а search
будет ?id=1
.
Затем мы можем получить различные параметры поиска, используя такие пакеты, как строка запроса, или путем их кодирования.
Хук useParams
Если мы настроим наш маршрут с параметром URL в его атрибуте пути, мы можем оценить эти параметры как пары ключ/значение с помощью хука useParams
.
Например, предположим, что у нас есть следующий Route.
<Route path='/employees/:id' > <Employees /> </Route>
Route будет ожидать динамический идентификатор вместо :id
.
С помощью хука useParams
мы можем оценить идентификатор, переданный пользователем, если он есть.
Например, если пользователь передает следующее в функцию с history.push
,
function goToPage = () => { history.push(`/employee/3`) }
Мы можем использовать хук useParams
для доступа к этому параметру URL следующим образом.
import {useParams} from 'react-router-dom' const ParamsExample = () =>{ const params = useParams() console.log(params) return( <div> ... </div> ) } export default ParamsExample
Если мы params
в консоль, мы получим следующий объект {id: "3"}
.
Хук useRouteMatch
Этот хук обеспечивает доступ к объекту соответствия. Он возвращает ближайшее совпадение с компонентом, если ему не передан аргумент.
Объект match возвращает несколько параметров, включая path
(такой же, как путь, указанный в Route), URL
, объект params
и isExact
.
Например, мы можем использовать useRouteMatch
для возврата компонентов на основе маршрута.
import { useRouteMatch } from "react-router-dom"; import Employees from "..."; import Admin from "..." const CustomRoute = () => { const match = useRouteMatch("/employees/:id"); return match ? ( <Employee /> ) : ( <Admin /> ); }; export default CustomRoute;
В приведенном выше коде мы устанавливаем путь маршрута с помощью useRouteMatch
а затем визуализируем компонент <Employee />
или <Admin />
в зависимости от маршрута, выбранного пользователем.
Чтобы это работало, нам все еще нужно добавить маршрут в наш файл App.js
... <Route> <CustomRoute /> </Route> ...
Создание пользовательского хука
Согласно документации React, создание пользовательского хука позволяет нам извлечь логику в повторно используемую функцию. Однако вам нужно убедиться, что все правила, применимые к хукам React, применимы и к вашему пользовательскому хуку. Ознакомьтесь с правилами хука React в начале этого руководства и убедитесь, что ваш пользовательский хук соответствует каждому из них.
Пользовательские хуки позволяют нам писать функции один раз и повторно использовать их всякий раз, когда они необходимы, и, следовательно, подчиняются принципу DRY.
Например, мы могли бы создать собственный хук для получения позиции прокрутки на нашей странице следующим образом.
import { useLayoutEffect, useState } from "react"; export const useScrollPos = () => { const [scrollPos, setScrollPos] = useState({ x: 0, y: 0 }); useLayoutEffect(() => { const getScrollPos = () => setScrollPos({ x: window.pageXOffset, y: window.pageYOffset }); window.addEventListener("scroll", getScrollPos); return () => window.removeEventListener("scroll", getScrollPos); }, []); return scrollPos; };
Здесь мы определили пользовательский хук для определения положения прокрутки на странице. Для этого мы сначала создали состояние scrollPos
для хранения положения прокрутки. Поскольку это будет изменять DOM, нам нужно использовать useLayoutEffect
вместо useEffect
. Мы добавили прослушиватель событий прокрутки для захвата позиций прокрутки x и y, а затем очистили прослушиватель событий. Наконец, мы вернулись к позиции прокрутки.
Мы можем использовать этот пользовательский хук в любом месте нашего приложения, вызвав его и используя его так же, как мы использовали бы любое другое состояние.
import {useScrollPos} from './Scroll' const App = () =>{ const scrollPos = useScrollPos() console.log(scrollPos.x, scrollPos.y) return ( ... ) } export default App
Здесь мы импортировали созданный выше пользовательский хук useScrollPos
. Затем мы инициализировали его, а затем записали значение в нашу консоль. Если мы прокручиваем страницу, хук будет показывать нам позицию прокрутки на каждом шаге прокрутки.
Мы можем создавать собственные хуки, чтобы делать практически все, что мы можем себе представить в нашем приложении. Как видите, нам просто нужно использовать встроенный хук React для выполнения некоторых функций. Мы также можем использовать сторонние библиотеки для создания пользовательских хуков, но если мы это сделаем, нам придется установить эту библиотеку, чтобы иметь возможность использовать хук.
Заключение
В этом руководстве мы рассмотрели некоторые полезные хуки React, которые вы будете использовать в большинстве своих приложений. Мы рассмотрели, что они представляют и как использовать их в вашем приложении. Мы также рассмотрели несколько примеров кода, чтобы помочь вам понять эти хуки и применить их к вашему приложению.
Я рекомендую вам попробовать эти хуки в вашем собственном приложении, чтобы лучше понять их.
Ресурсы из документации React
- Часто задаваемые вопросы по хукам
- Редукс Инструментарий
- Использование хука состояния
- Использование хука эффектов
- Справочник API хуков
- Реагировать на Redux-хуки
- Реагировать на хуки маршрутизатора