Блог экспериментатора инженера-разработчика: Infanty.
Я пишу 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';   
  <a onClick={props.onClick} href="#" title="Play video" className={className} />;
); 

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

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

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

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

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

    /* Стартовое значение набора переменных состояния. */
    this.state = {
      weatherData: null
    };
  }
  
  /* Вызывается сразу после монтирования компонента (в DOM дерево). */
  componentDidMount() {
    /* Считывание переданных свойств в компонент (данный компонент должен быть вызван подобным образом: <WeatherDisplay zip="13597" />). */
    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>;
  }
}