React TS – Как использовать контексты React для управления состоянием
Контексты играют ключевую роль в разработке React. Они обеспечивают эффективное управление общим состоянием и данными между компонентами, обеспечивая бесперебойную связь между различными частями вашего приложения. Понимание того, как использовать возможности контекстов, имеет первостепенное значение для создания надежных и удобных в обслуживании приложений React.
Что такое контексты?
React Context — это механизм для эффективной передачи данных через дерево компонентов без ручной передачи реквизитов на каждом уровне. Думайте о них как о глобальном хранилище данных вашего приложения.
Чтобы проиллюстрировать это повседневным примером, представьте, что вы планируете семейную поездку. Члены вашей семьи представляют разные компоненты вашего приложения. Вместо того, чтобы звонить каждому члену семьи индивидуально и сообщать ему подробности поездки, вы можете использовать доску (контекст) в гостиной, где каждый сможет увидеть планы.
Теперь, если вы обновите время отправления на доске, все сразу узнают об изменении.
В React это похоже на обновление данных в контексте, и любой компонент, который подписывается на этот контекст, автоматически получает обновленную информацию без необходимости прямого взаимодействия между компонентами. Он оптимизирует обмен данными и обеспечивает единообразие всего вашего приложения, подобно удобной доске для планирования семейной поездки.
Преимущества
Контексты предлагают множество преимуществ при разработке React, что делает их незаменимым инструментом для создания масштабируемых и поддерживаемых приложений. Некоторые ключевые преимущества включают в себя:
- Simplified Data Sharing: контексты устраняют необходимость в сверлении опор, что позволяет легко обмениваться данными между компонентами на разных уровнях дерева компонентов.
- Cleaner Code: они поощряют чистый, модульный код, отделяя проблемы данных от проблем представления, что приводит к созданию более удобных в обслуживании и понятных кодовых баз.
- Global State Management: Контексты превосходно справляются с управлением глобальным состоянием, гарантируя, что критически важные данные приложения остаются согласованными и доступными во всем приложении.
- Improved Performance: Благодаря интеллектуальному обновлению только тех компонентов, которые полагаются на измененные данные, контексты помогают оптимизировать производительность, сокращая ненужные повторные рендеринги.
- Code Readability: Использование контекстов для управления состоянием повышает читаемость кода, облегчая понимание структуры и потока вашего приложения.
Включение контекстов в ваши проекты React позволяет вам создавать более эффективные, удобные в обслуживании и масштабируемые приложения, что в конечном итоге приводит к улучшению опыта разработки и повышению удовлетворенности пользователей.
Практический случай
Соревнование
Представьте, что мы создаем погодное приложение, которое отображает текущие погодные условия в разных городах. Данные о погоде каждого города состоят из его названия, температуры и описания погоды.
Вот структура нашего приложения:
Теперь, если мы хотим получить данные в SearchBar.tsx
и отобразить их в CurrentWeather.container.tx
и WeekWeather.container.tsx
без контекста, нам нужно создать состояние в верхней части нашего App.tsx
:
// App.tsx
export type WeatherWeekData = {
temperature: number
}
export type WeatherData = {
town: string
current: WeatherWeekData
week: WeatherWeekData[]
}
function App() {
const [weatherData, setWeatherData] = useState<WeatherData | null>(null)
return (
<div className="App">
<Header />
<SearchBar setWeatherData={setWeatherData} />
{weatherData && <CurrentWeather town={weatherData.town} temperature={weatherData.current.temperature} />}
{weatherData && <WeekWeather town={weatherData.town} week={weatherData.week} />}
<Footer />
</div>
)
}
export default App
// SearchBar.tsx
type SearchBarProps = {
setWeatherData: (weatherData: WeatherData) => void
}
export const SearchBar = ({ setWeatherData }: SearchBarProps) => {
const [searchTerm, setSearchTerm] = useState<string>('')
const handleOnInputChange = (event: ChangeEvent<HTMLInputElement>) => {
// wait 1s delay before set the term and fetch data
setTimeout(() => {
setSearchTerm(event.target.value)
}, 1000)
}
const fetchData = (term: string) => {
fetch('https://mysuperAPI.com/search?term=' + term)
.then((response) => response.json())
.then((data) => setWeatherData(data))
}
useEffect(() => {
fetchData(searchTerm)
}, [searchTerm])
return (
<div>
<input placeholder="Find your town" value={searchTerm} onChange={handleOnInputChange} />
</div>
)
}
// CurrentWeather.container.tsx
type CurrentWeatherProps = {
town: string
temperature: number
}
export const CurrentWeather = ({ town, temperature }: CurrentWeatherProps) => {
return (
<div>
<h1>Current Weather</h1>
<div>
<h2>{town}</h2>
<p>{temperature} °F</p>
</div>
</div>
)
}
// WeekWeather.container.tsx
type WeekWeatherProps = {
town: string
week: WeatherWeekData[]
}
export const WeekWeather = ({ town, week }: WeekWeatherProps) => {
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
return (
<div>
<h1>WeekWeather Weather</h1>
<div>
<h2>{town}</h2>
<div>
{week.map((day, index) => (
<div>
<h3>Day : {days[index]}</h3>
<p>{day.temperature} °F</p>
</div>
))}
</div>
</div>
</div>
)
}
Как мы видим, нам нужно поделиться состоянием и setState, чтобы гарантировать, что данные есть у других дочерних компонентов. К сожалению, это решение не подлежит ремонту. Повсюду разбросаны реквизиты, а данные на самом деле не централизованы.
Решение
Чтобы получить более чистый код, мы создадим контекст! Целью этого является хранение данных так, чтобы к ним могли получить доступ все компоненты.
Состав:
Создайте папку src/contexts/
и создайте 3 файла в папке контекста следующим образом:
Пояснения
- Weather actions: список наших действий, кроме
GET: setWeather
,setFavoriteTown
и других… - Weather provider: он будет охватывать ту часть вашего приложения, которая требует доступа к данным. Оно также управляет самим государством.
- Weather reducer: он управляет различными действиями, поэтому, если вы хотите изменить или добавить данные, это его дело.
Создайте все файлы:
Действия
// weather.actions.ts
import { WeatherData } from './weather.reducer'
export enum EWeatherActions {
SET_WEATHER = 'SET_WEATHER'
}
type SetWeather = {
type: EWeatherActions.SET_WEATHER
payload: WeatherData
}
export const setWeather = (args: WeatherData): SetWeather => ({
type: EWeatherActions.SET_WEATHER,
payload: args
})
export type WeatherActions = SetWeather
Хорошо знать:
setWeather
— это действие, которое мы должны вызвать, если хотим добавить данные в наш контекст.- Мы экспортируем тип, чтобы набрать наш редуктор.
Редуктор
import { Reducer } from 'react'
import { EWeatherActions, WeatherActions } from './weather.actions'
export type WeatherWeekData = {
temperature: number
}
export type WeatherData = {
town: string
current: WeatherWeekData | null
week: WeatherWeekData[]
}
export type WeatherState = {
weather: WeatherData | null
}
export const initialState: WeatherState = {
weather: null
}
export const weatherReducer: Reducer<WeatherState, WeatherActions> = (state = initialState, action) => {
switch (action.type) {
case EWeatherActions.SET_WEATHER:
return {
...state,
...action.payload
}
default:
return { ...state }
}
}
Хорошо знать:
- Существует много типов, но наиболее важными являются
InitialState
иWeatherReducer
. InitialState
: как и в названии, это начальное состояние нашего контекста. Мы просто добавили к нашим данным погодный объект.WeatherReducer
: это простойswitch / case
по типу действия.
Provider
// weather.provider.ts
import { createContext, Dispatch, ReactNode, useContext, useMemo, useReducer } from 'react'
import { initialState, weatherReducer, WeatherState } from './weather.reducer'
import { WeatherActions } from './weather.actions'
type WeatherContext = WeatherState & {
dispatch: Dispatch<WeatherActions>
}
const weatherContext = createContext<WeatherContext>({ ...initialState, dispatch: () => {} })
export const useWeatherContext = () => useContext(weatherContext)
type WeatherProviderProps = {
children: ReactNode
}
export const WeatherProvider = ({ children }: WeatherProviderProps) => {
const [state, dispatch] = useReducer(weatherReducer, initialState)
const value: WeatherContext = useMemo(() => ({ ...state, dispatch }), [state])
return <weatherContext.Provider value={value}>{children}</weatherContext.Provider>
}
Хорошо знать:
Weathercontext
: Не важная переменная, просто создатьWeatherProvider
.useWeatherContext
: это псевдоним, ярлык для вызова нашегоuseContext
.WeatherProvider
: В нашем штате нам нужно окружить ту часть нашего приложения, которая требует данных, чтобы ограничить доступ и повысить производительность.
Используйте наш контекст!
Наш новый App.tsx:
// App.tsx
function App() {
return (
<div className="App">
<Header />
<WeatherProvider>
<SearchBar />
<CurrentWeather />
<WeekWeather />
</WeatherProvider>
<Footer />
</div>
)
}
export default App
Мы удалили все реквизиты и включили часть «Погода» в WeatherProvider для обмена данными.
Чтобы установить данные:
// SearchBar.tsx
export const SearchBar = () => {
const { dispatch: dispatchWeather } = useWeatherContext()
const [searchTerm, setSearchTerm] = useState<string>('')
const handleOnInputChange = (event: ChangeEvent<HTMLInputElement>) => {
// wait 1s delay before set the term and fetch data
setTimeout(() => {
setSearchTerm(event.target.value)
}, 1000)
}
const fetchData = (term: string) => {
fetch('https://mysuperAPI.com/search?term=' + term)
.then((response) => response.json())
.then((data) => dispatchWeather(setWeather(data)))
}
useEffect(() => {
fetchData(searchTerm)
}, [searchTerm])
return (
<div>
<input placeholder="Find your town" value={searchTerm} onChange={handleOnInputChange} />
</div>
)
}
В этом файле мы берем отправку из useWeatherContext. Dispatch — это функция, которая позволяет вам использовать одно из определенных нами действий. Здесь мы берем диспетчер и переименовываем его в sendWeather. Переименование диспетчеризации упрощает отладку, когда у нас много контекстов и диспетчеризации.
Чтобы использовать данные:
// CurrentWeather.container.tsx
export const CurrentWeather = () => {
const { weather } = useWeatherContext()
if (!weather) return <div>Please select a town.</div>
return (
<div>
<h1>Current Weather</h1>
<div>
<h2>{weather.town}</h2>
<p>{weather.current.temperature} °F</p>
</div>
</div>
)
}
// WeekWeather.container.tsx
export const WeekWeather = () => {
const { weather } = useWeatherContext()
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
if (!weather) return <div>Please select a town.</div>
return (
<div>
<h1>WeekWeather Weather</h1>
<div>
<h2>{weather.town}</h2>
<div>
{weather.week.map((day, index) => (
<div>
<h3>Day : {days[index]}</h3>
<p>{day.temperature} °F</p>
</div>
))}
</div>
</div>
</div>
)
}
Вот и все! Мы создали и использовали собственный чистый контекст! Поздравляю.
Идти дальше
Освоив основы контекстов React, вы сможете вывести разработку приложений на новый уровень, изучив дополнительные темы:
- localStorage с контекстами: объедините возможности контекстов с localStorage для сохранения состояния приложения. Это особенно полезно для сохранения пользовательских настроек, таких как выбор тем, пользовательских настроек или даже последнего состояния корзины покупок пользователя. Связывая контексты с localStorage, вы гарантируете сохранение пользовательских данных между сеансами. Это расширяет возможности пользователя, обеспечивая непрерывность и персонализацию.
- Интеграция с Redux: Хотя контексты React отлично подходят для управления состоянием локального уровня компонентов, Redux — это надежная библиотека управления состоянием, которая превосходно справляется с управлением глобальным состоянием всего вашего приложения. Вы можете использовать как Redux для общего состояния приложения, так и контексты для более конкретного управления состоянием на уровне компонентов. Этот гибридный подход сочетает в себе лучшее из обоих миров, позволяя эффективно управлять данными и обмениваться ими между компонентами, сохраняя при этом глобальное хранилище состояний для сложных данных уровня приложения.
- Тестирование и отладка: изучите инструменты и методы тестирования и отладки приложений, использующих контексты. Такие библиотеки, как React Testing Library и Redux DevTools, могут оказаться невероятно ценными для обеспечения надежности и производительности вашего кода.
Заключение
Подводя итог, можно сказать, что контексты React имеют решающее значение для эффективного обмена данными в ваших приложениях. Они упрощают код, эффективно управляют глобальным состоянием и повышают производительность. На практическом примере мы увидели, как использование контекстов может существенно очистить ваш код. Освоив контексты, вы сможете создавать более эффективные и удобные в обслуживании приложения, не теряя при этом интереса читателей.