Разбираемся в React Context

Разбираемся в React Context

От автора: в последнее время вы, возможно, задумывались над тем, что React Context у всех на слуху и что это может означать для вас и ваших сайтов на React. До Context, когда управление состоянием становилось сложнее, чем позволял сделать функционал setState, вам, вероятно, приходилось использовать стороннюю библиотеку. Благодаря недавним обновлениям команды Awesome React, у нас теперь есть Context, который может помочь в решении некоторых проблем управления состоянием.

Что решает Context?

Как передать состояние из родительского компонента в дочерний, который вложен в дерево компонентов? Вы знаете, что мы можем использовать Redux для управления состоянием, но вам не обязательно прибегать к Redux в каждой ситуации.

Есть способ сделать это без использования Redux или любого другого стороннего инструмента управления состоянием. Вы можете использовать свойство! Скажем, функция, которую вы хотите реализовать, имеет древовидную структуру, похожую на эту:

Состояние находится в компоненте App и требуется для компонентов UserProfile и UserDetails. Вам нужно передать его через свойство вниз по дереву. Если компоненты, которые нуждаются в этом состоянии, имеют глубину до 10 уровней, это может быть сложно, утомительно и чревато ошибками. Каждый компонент должен быть как черный ящик — другие компоненты не должны знать о состояниях, которые им не нужны. Ниже приведен пример приложения, которое соответствует приведенному выше сценарию.

class App extends React.Component {
  state = {
 user: {
 username: 'jioke',
 firstName: 'Kingsley',
 lastName: 'Silas'
 }
  }

  render() {
 return(
 <div>
 <User user={this.state.user} />
 </div>
 )
  }
}

const User = (props) => (
  <div>
 <UserProfile {...props.user} />
  </div>
)

const UserProfile = (props) => (
  <div>
 <h2>Profile Page of {props.username}</h2>
 <UserDetails {...props} />
  </div>
)

const UserDetails = (props) => (
  <div>
 <p>Username: {props.username}</p>
 <p>First Name: {props.firstName}</p>
 <p>Last Name: {props.lastName}</p>
  </div>
)

ReactDOM.render(<App />, document.getElementById("root"));

Мы передаем состояние от одного компонента к другому с помощью свойства. Пользовательский компонент не нуждается в состоянии, но он должен получить его через свойства, чтобы состояние можно было передать вниз по дереву. Это именно то, чего мы хотим избежать.

Context приходит на помощь!

Context API React позволяет сохранять состояние в том, что выглядит как глобальное состояние приложения и доступ к нему осуществляется только в тех компонентах, которые в нем нуждаются, без необходимости передавать его через свойства. Мы начинаем с инициализации нового Context с использованием createContext():

const UserContext = React.createContext({})
const UserProvider = UserContext.Provider
const UserConsumer = UserContext.Consumer

Этот новый Context присваивается переменной const, в данном случае это переменная UserContext. Вы видите, что нам нет необходимости устанавливать библиотеку, когда createContext() доступен в React (16.3.0 и выше).

Компонент Provider делает Context доступным для компонентов, которые в нем нуждаются — они называются Subscribers. Другими словами, компонент Provider позволяет Consumers подписываться на изменения в Context. Помните, что Context похож на глобальное состояние приложения. Таким образом, компоненты, которые не являются Consumers, не будут подписаны на контекст. Если вы кодируете локально, ваш файл контекста будет выглядеть так:

import { createContext } from 'react'

const UserContext = createContext({})
export const UserProvider = UserContext.Provider
export const UserConsumer = UserContext.Consumer

Provider

Мы будем использовать Provider в родительском компоненте, где у содержится наше состояние.

class App extends React.Component {
  state = {
 user: {
 username: 'jioke',
 firstName: 'Kingsley',
 lastName: 'Silas'
 }
  }

  render() {
 return(
 <div>
 <UserProvider value={this.state.user}>
 <User />
 </UserProvider>
 </div>
 )
  }
}

Provider принимает значение, которое будет передано его дочерним Consumer-компонентам. В этом случае мы передадим пользовательское состояние компонентам Consumer. Вы видите, что мы не передаем состояние компоненту User в качестве свойства. Это означает, что мы можем редактировать компонент User и исключать свойства, так как они ему не нужны:

const User = () => (
  <div>
 <UserProfile />
  </div>
)

Consumer

Несколько компонентов могут подписаться на один компонент Provider. Наш компонент UserProfile должен использовать контекст, поэтому он будет подписан на него.

const UserProfile = (props) => (
  <UserConsumer>
 {context => {
 return(
 <div>
 <h2>Profile Page of {context.username}</h2>
 <UserDetails />
 </div>
 )
 }}
  </UserConsumer>
)

Затем данные, которые мы ввели в Provider через значение свойства, будут доступны через параметры контекста функции. Теперь мы можем использовать имя пользователя этого пользователя в нашем компоненте.

Компонент UserDetails будет похож на компонент UserProfile, поскольку он является подписчиком одного и того же компонента Provider:

const UserDetails = () => (
  <div>
 <UserConsumer>
 {context => {
 return (
 <div>
 <p>Userame: {context.username}</p>
 <p>First Name: {context.firstName}</p>
 <p>Last Name: {context.lastName}</p>
 </div>
 )
 }}
 </UserConsumer>
  </div>
)

Обновление состояния

Но что делать, если мы хотим предоставить пользователям возможность изменять свои имя и фамилию? Это также возможно. Компоненты Consumer могут повторно отображаться каждый раз, когда происходят изменения в значении, передаваемом компонентом Provider. Давайте рассмотрим пример.

В компоненте Consumer у нас будет два поля ввода для имени и фамилии. Из компонента Provider мы будем получать два метода, которые обновляют состояние приложения, используя значения, введенные в поля ввода. Достаточно разговоров, давайте напишем код! Наш компонент приложения будет выглядеть так:

class App extends React.Component {
  state = {
 user: {
 username: 'jioke',
 firstName: 'Kingsley',
 lastName: 'Silas'
 }
  } 

  render() {
 return(
 <div>
 <UserProvider value={
 {
 state: this.state.user,
 actions: {
 handleFirstNameChange: event => {
 const value = event.target.value
 this.setState(prevState => ({
 user: {
 ...prevState.user,
 firstName: value
 }
 }))
 },

 handleLastNameChange: event => {
 const value = event.target.value
 this.setState(prevState => ({
 user: {
 ...prevState.user,
 lastName: value
 }
 }))
 }
 }
 }
 }>
 <User />
 </UserProvider>
 </div>
 )
  }
}

Мы передаем объект, который содержит состояние и действия, в значения свойств, которые получает Provider. Действия — это методы, которые будут запускаться при возникновении события onChange. Затем значение события используется для обновления состояния. Поскольку мы хотим обновить либо имя, либо фамилию, необходимо сохранить значение обоих. Для этого мы используем ES6 Spread Operator, который позволяет нам обновить значение указанного ключа. Теперь нам нужно соответствующим образом обновить компонент UserProfile.

const UserProfile = (props) => (
  <UserConsumer>
 {({state}) => {
 return(
 <div>
 <h2>Profile Page of {state.username}</h2>
 <UserDetails />
 </div>
 )
 }}
  </UserConsumer>
)

Мы используем деструктурирование ES6 для извлечения состояния из значения, полученного от Provider. Для компонента UserDetails мы извлекаем и состояние, и действия. Нам также нужно добавить два поля ввода, которые будут прослушивать событие onChange() и вызвать соответствующие методы.

const UserDetails = () => {
  return (
 <div>
 <UserConsumer>
 {({ state, actions }) => {
 return (
 <div>
 <div>
 <p>Userame: {state.username}</p>
 <p>First Name: {state.firstName}</p>
 <p>Last Name: {state.lastName}</p>
 </div>
 <div>
 <div>
 <input type="text" value={state.firstName} onChange={actions.handleFirstNameChange} />
 </div>
 <div>
 <input type="text" value={state.lastName} onChange={actions.handleLastNameChange} />
 </div>
 </div>
 </div>
 )
 }}
 </UserConsumer>
 </div>
  )
}

Использование значений по умолчанию

При инициализации Context можно передавать значения по умолчанию. Чтобы сделать это, вместо передачи пустого объекта createContext() мы передадим некоторые данные.

const UserContext = React.createContext({
  username: 'johndoe',
  firstName: 'John',
  lastName: 'Doe'
})

Чтобы использовать эти данные в дереве приложений, нам нужно удалить провайдер из дерева. Таким образом, наш компонент приложения будет выглядеть так.

class App extends React.Component {
  state = {
 user: {
 username: 'jioke',
 firstName: 'Kingsley',
 lastName: 'Silas'
 }
  }

  render() {
 return(
 <div>
 <User />
 </div>
 )
  }
}

Данные, которые будут использоваться в компонентах Consumer, будут определены при инициализации нового Context.

В заключение

Когда все усложняется, и у вас возникает желание запустить yarn install [<insert third-party library for state management], сделайте паузу на секунду — у вас есть React Context.

Автор: Kingsley Silas

Источник: https://css-tricks.com/

Редакция: Команда webformyself.

Фреймворк VUE JS

VUE JS - полное руководство для современной веб-разработки

Научиться

Метки:

Похожие статьи:

Комментарии Вконтакте:

Комментарии Facebook:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Я не робот.

Spam Protection by WP-SpamFree