Привет, меня зовут Infanty, мой профиль в LinkedIn.
Я пишу how-to статьи на редкие темы или статьи обзоры - для себя и тех кто со мной работает.

В предшествующих статьях мы уже познакомились:

  • с основами React.js;
  • с основами шаблонизации макетов сайтов на базе функциональных компонентов React.js не имеющих состояний;
  • настроили IDE и проверку кода создаваемого React.js приложения;
  • создали базовое приложения на базе Create React App (CRA);
  • добавили в React.js приложение поддержку SASS подобной вёрстки, используя PostCSS;
  • познакомились с вёрсткой по методологии БЭМ.

Зная как разбить макет сайта на шаблоны на базе функциональных компонентов React.js и как именовать CSS классы шаблонов используя методологию БЭМ, мы можем создать "небольшой" сайт на основе CRA используя настроенный инструментарий.

Но как быть с разработкой "большого" сайта (приложения):

  • как передавать в компоненты изменяемые данные для вывода - например: имя текущего пользователя для компонента шаблонизирующего блок с информацией о пользователе или текст статьи для компонента шаблонизирующего блок центральной части страницы-статьи;
  • как добавить компоненту интерактивности (работая с его состояниями) - например: показывать или скрывать часть данных компонента по нажатию кнопки;
  • как производить обмен данными между компонентами и автоматически обновлять зависимые (от текущего компонента) компоненты;
  • как добавить в компонент логику, что бы компонент получал данные с бэкенда или стороннего сайта и далее передавал их интерактивному компоненту, а тот в свою очередь на основе этих данных - отображал нужный функциональный компонент (не имеющий состояний) с передачей ему свойств из этих данных.

Как передавать в компонент изменяемые данные
для вывода (используя его свойства (props))

В React.js поток данных - однонаправленный, т.е. данные передаются как водопад, сверху вниз, от родителя к ребенку, через props - неизменяемый объект, предназначенный только для чтения. Из которого компонент может читать переданные ему свойства. В CRA файл (например: "User.jsx") с компонентом будет иметь следующий код:

import React from 'react'

const User = ({ props }) => (
  <div className="user">
    Вы авторизовались как пользователь с именем: {props.userName}.
  </div>
)

export default User 

Или в оптимизированном варианте принимая в компонент не весь props, а только нужную переменную.

import React from 'react'

const User = ({ userName }) => (
  <div className="user">
    Вы авторизовались как пользователь с именем: {userName}.
  </div>
)

export default User 

Вызов такого компонента изменится с <User /> на <User userName="Admin" />.

Таким образом можно создать все компоненты и под-компоненты отвечающие за шаблонизацию вывода данных (например: центральную часть страницы со статьёй).

Как добавить компоненту интерактивности (работая с его состояниями (state))

Компонент может иметь свой набор переменных состояния (state), при этом можно задавать стартовые значения переменных состояния.

Состояние компонента удобно использовать для организации логики работы компонента:

  • выводить компонент с логином пользователя или компонент с формой авторизации;
  • показывать уменьшенный вариант статьи и разворачивать его по нажатию кнопки;
  • не активировать кнопку формы в компоненте пока не заполнено поле в данном компоненте;
  • и т.п. связи состояний ui элементов компонента.

Код примера подобного компонента (без import и export, как в предшествующем примере):

class Article extends React.Component {
  // Стартовые значения набора переменных состояния.
  state = {
    visible: false,
  }

  // Обработка нажатия на ссылку.
  handleReadMoreClck = (e) => { 
    // Отменяем действия браузера по умолчанию при нажатию на ссылку.
    e.preventDefault()
    // Изменяем значение перемеренной состояния.
    this.setState({ visible: true })
  }

  // Обработка нажатия на ссылку.
  handleReadMoreHide = (e) => { 
    // Отменяем действия браузера по умолчанию при нажатию на ссылку.
    e.preventDefault()
    // Изменяем значение перемеренной состояния.
    this.setState({ visible: false })
  }

  render() {
    // Получаем значение перемеренной состояния.
    const { visible } = this.state

    return (
      <div className="article">
        {!visible && (
          <a onClick={this.handleReadMoreClck} href="#" className="readmore">
            Показать
          </a>
        )}
        {visible && (
          <a onClick={this.handleReadMoreHide} href="#" className="hide">
            Спрятать
          </a>
        )}
      </div>
    )
  }
} 

Важно помнить что любое изменение state вызывает render компонента. А так же, что state текущего компонента можно передавать в props дочернего компонента.

Как производить обмен данными между компонентами и автоматически обновлять зависимые (от текущего компонента) компоненты

Рассмотрим пример, подобный примеру выше, но при этом добавим зависимость функционального компонента от интерактивного компонента:

class Container extends React.Component {
  // Конструктор класса.
  constructor(props) {
    // Вызов родительского конструктора класса.
    super(props)
    // Стартовые значения набора переменных состояния.
    this.state = { isMusicPlaying: false }
  }
  
  // Обработка нажатия на кнопку.
  handleClick() {
    // При вызове setState изменение состояния не производится мгновенно. Поэтому 
    // следует передать setState функцию, а не объект. Эта функция принимает старое 
    // состояние как аргумент и возвращает объект, представляющий новое состояние.
    this.setState(prevState => {
      return {
        isMusicPlaying: !prevState.isMusicPlaying,
      }
    })
  }
  
  render() {
    // Получаем значение из набора переменных перемеренных состояния.
    let status = this.state.isMusicPlaying ? 'Playing' : 'Not playing'
	
    return (
      <div>
        <h1 onClick={this.handleClick.bind(this)}>{ status }</h1>
        <PlayButton 
          // Передаём в дочерний компонент свойство, которое является функцией. 
          // Вызов этой функции в дочернем компоненте, изменяет состояние текущего компонента.
          // Тем самым осуществляется обратный обмен данными.
          onClick={this.handleClick.bind(this)}

          // Передаём в дочерний компонент свойство, которое является состоянием текущего компонента. 
          // При изменении этого состояния в текущем компоненте, перерисовывается и дочерний компонент. 
          // Тем самым осуществляется прямой обмен данными.
          isMusicPlaying={this.state.isMusicPlaying} 
        />
      </div>
    )
  }
} 

Код функционального компонента:

const PlayButton = props => {
  const className = props.isMusicPlaying ? 'play active' : 'play'

  return (
    <div>
      <a
        onClick={props.onClick}
        href="#"
        title="Play video"
        className={className}
      >
        Play video
      </a>
    </div>
  )
}

Прямой обмен данными: когда состояние класса Container меняется, свойство передаваемое PlayButton - также меняется, и функция PlayButton вызывается снова. Это означает, что вид компонента на экране обновится, т.е. у ссылки Play video изменится CSS класс (согласно коду функционального компонента).

Обратный обмен данными: когда мы щёлкаем по ссылке в кнопке PlayButton (по ссылке Play video), выполняется переданная в виде свойства функция, которая меняет состояние класса Container, а далее идёт прямой обмен данными - которое изменит props у PlayButton, что приведёт к обновлению кнопки на странице.

Как добавить в компонент логику (используя жизненный цикл компонента)

Что бы компонент получал данные с бэкенда или стороннего сайта и далее передавал их интерактивному компоненту - воспользуемся событием монтирования компонента:

class WeatherDisplay extends React.Component {
  // Конструктор класса.
  constructor() {
    // Вызов родительского конструктора класса.
    super()

    // Стартовое значение набора переменных состояния.
    this.state = {
      weatherData: null,
    }
  }
  
  // Вызывается сразу после монтирования компонента (в DOM дерево).
  componentDidMount() {
    // Считывание переданных свойств в компонент (данный 
    // компонент должен быть вызван подобным образом: ).
    const zip = this.props.zip
    const URL =
      'http://api.openweathermap.org/data/2.5/weather?q=' +
      zip +
      '&appid=b1b35bba8b434a28a0be2a3e1071ae5b&units=imperial'

    // Осуществляем запрос по URL.
    fetch(URL)
      .then(res => res.json())
      .then(json => {
        // Изменяем значение в наборе перемеренных состояния, сохраняя в нем полученные данные.
        this.setState({ weatherData: json })
      })
  }
  
  render() {
    // Получаем значение из набора переменных перемеренных состояния.
    const weatherData = this.state.weatherData
	
    // Если данные отсутствуют.
    if (!weatherData) return <div>Loading</div>

    // Если данные получены.
    return <div>{JSON.stringify(weatherData)}</div>
  }
}