Допустим, вы уже знаете, как писать ванильный Javascript или jQuery, чтобы делать что-то похожее на приложение. Вы можете получать данные, отправлять данные и манипулировать DOM без каких-либо фреймворков. Зачем вам изучать React, если вы уже можете делать то, что хотите, без него?

Что такое Реакт?

React — это библиотека Javascript, упрощающая создание пользовательских интерфейсов для одностраничных приложений. Вы создаете компоненты, которые в конечном итоге работают как ваши собственные пользовательские теги HTML прямо в вашем Javascript.

Вы пишете что-то, что выглядит как HTML, прямо в коде React. Он называется JSX. Вы можете включить выражения Javascript в свой JSX, чтобы вы могли делать интересные вещи, такие как вывод данных в отображаемом компоненте по мере его изменения.

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

Ваш компонент может иметь состояние. Это набор переменных, в которых хранятся данные, важные для этого компонента. Если состояние вашего компонента изменяется, вы можете обновить отображаемый компонент, чтобы отразить изменение состояния.

Каждый компонент имеет жизненный цикл. Вы можете написать свой собственный код внутри предопределенных методов жизненного цикла вашего компонента. Когда происходит событие, описанное этим методом, React запускает ваш метод жизненного цикла. Вот несколько примеров методов жизненного цикла и того, что их запускает:

  • constructor()– Запускается сразу после создания компонента.
  • componentDidMount()– Запускается после добавления компонента в DOM. Это отличное место для получения данных из базы данных или внешнего API для использования в вашем компоненте и добавления их в состояние компонента.
  • componentDidUpdate()– Запускается, когда компонент обновляется React. Этот будет запускаться при изменении состояния компонента или когда одно из свойств компонента получает новое значение.

Это React в двух словах, хотя это обширная тема. Эта статья не является учебным пособием; Я просто пытаюсь дать вам достаточно контекста, чтобы показать вам, почему это полезно. Если вы хотите изучить React, вы можете начать с официального руководства.

Обновление данных в DOM — это головная боль 🍑

Причина, по которой обновление данных является проблемой, на самом деле не в том, что это сложно с ванильным Javascript; более того, трудно рассуждать постфактум. Фактический процесс довольно прост. Я просто выбираю элемент и меняю его innerHTMLproperty. Вуаля! Данные обновлены.

const elementToChange = document.querySelector('h1');
elementToChange.innerHTML = "New Heading Value";

Теперь, когда нам нужно вернуться позже, чтобы поддерживать это приложение, нам нужно просмотреть все различные места, где мы обновили DOM. Если данные на странице отображаются неправильно, это может быть проблемой в любой из этих точек. Передача данных напрямую в DOM через innerHTML также опасна, потому что злоумышленник потенциально может запустить свой собственный код на вашем сайте и украсть информацию у ваших пользователей.

В React мы поддерживаем данные, которые важны для приложения, в его состоянии, и мы позволяем React беспокоиться об обновлении DOM по мере изменения этих данных.

Широко поддерживаемые компоненты

Подумайте о форме входа. В наших головах это одно, но в Интернете это обычно пять разных элементов: две метки, два поля ввода и кнопка.

Было бы неплохо, если бы вы могли думать об этом как об одном компоненте, даже когда вы создаете свое приложение? Всем хороших новостей! Веб-компоненты позволяют вам делать это без каких-либо фреймворков… до тех пор, пока вам нужна поддержка только более поздних версий Chrome, Firefox и Opera.

Это здорово, но, скорее всего, вам понадобится более широкая поддержка браузера. React позволяет создавать компоненты, которые работают с IE 9+ и всеми современными браузерами, так что в следующий раз, когда вы захотите добавить форму входа в представление, вы можете просто добавить форму входа. сильно> вот так:

<SignInForm />

Вам придется создать этот компонент один раз самостоятельно (поскольку React изначально не знает, какой «SignInForm» должна быть в вашем приложении), но дело в том, что у вас есть возможность создать его только один раз и используйте его столько раз, сколько захотите. То, как он выглядит, и его поведение будут сопровождать его повсюду.

Нужна помощь в том, чтобы стать веб-разработчиком? Я могу помочь независимо от того, где вы находитесь в вашем переходе. Закажите бесплатную менторскую сессию в RadDevon!

Пример: время восхода солнца

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

Время восхода солнца (ванильный JS)

Вот Javascript для ванильной версии приложения. Если вы хотите увидеть HTML и CSS, посмотрите эти вкладки в демо-версии Codepen, встроенной ниже.

function debounced(delay, fn) {
  let timerId;
  return function(...args) {
    if (timerId) {
      clearTimeout(timerId);
    }
    timerId = setTimeout(() =&gt; {
      fn(...args);
      timerId = null;
    }, delay);
  };
}
function updateTimes(lat, long) {
  if (lat &amp;&amp; long) {
    return fetch(`https://api.sunrise-sunset.org/json?lat=${lat}&amp;lng=${long}`)
      .then(response =&gt; {
        if (!response.ok) {
          sunriseTimeElement.innerHTML = "Invalid";
          sunsetTimeElement.innerHTML = "Invalid";
          throw Error(`${response.status} response on times request`);
        }
        return response.json();
      })
      .then(data =&gt; {
        sunriseTimeElement.innerHTML = data.results.sunrise;
        sunsetTimeElement.innerHTML = data.results.sunset;
      })
      .catch(error =&gt; {
        console.error(error.message);
      });
  }
}
function updateTimesFromInput() {
  const lat = latField.value;
  const long = longField.value;
  updateTimes(lat, long);
}
const updateTimesFromInputDebounced = debounced(500, updateTimesFromInput);
const sunriseTimeElement = document.querySelector(".sunrise .time");
const sunsetTimeElement = document.querySelector(".sunset .time");
const latField = document.querySelector("#lat");
const longField = document.querySelector("#long");
navigator.geolocation.getCurrentPosition(function(position) {
  const lat = position.coords.latitude;
  const long = position.coords.longitude;
  latField.value = lat;
  longField.value = long;
  updateTimes(lat, long);
});
[latField, longField].forEach(field =&gt; {
  const events = ["keyup", "change", "input"];
  events.forEach(event =&gt; {
    field.addEventListener(event, updateTimesFromInputDebounced);

См. Pen Sunrise app (ванильный вариант) Девона Кэмпбелла (@raddevon) на CodePen.

Вы заметите несколько приятных вещей в этой версии. Это намного короче, чем версия React. Это отчасти потому, что он не использует компоненты (которые добавляют некоторые накладные расходы в версии React), а отчасти потому, что все состояние находится в документе (именно это затрудняет поддержку и анализ, когда что-то идет не так).

Некоторые другие примечания, которые могут помочь вам понять контекст:

  • Я использовал числовые поля, поэтому мне не нужно проверять. На самом деле, мне все еще следует выполнять проверку, так как не все браузеры поддерживают числовые поля, но я не беспокоюсь о том, чтобы сделать этот пример готовым к работе.
  • Весь документ (заголовок, форма и вывод) уже представлен в HTML-документе. Все, что мы делаем в Javascript задним числом, — это обновляем значения выходов после получения результатов от новых входов.
  • Поскольку мой код зависит от выбора правильных элементов, приложение может сломаться, если кто-то пойдет позади меня и изменит классы или идентификаторы элементов. Вот так я их выбираю. Поскольку там сохраняется состояние, если я не могу получить к ним доступ, приложение не может делать то, что ему нужно.
  • Обратите внимание на утомительный способ привязки нескольких событий, чтобы убедиться, что я фиксирую любые изменения в полях lat и long. Числовые поля генерируют разные события в зависимости от того, как они были изменены, поэтому мне нужно создать привязку для каждого из них. (Вы увидите, что я имею в виду в конце кода Javascript.)
  • Вы заметите странную функцию debounced как в этой, так и в версии React этого приложения. Время пришло из API. Я не хочу изнурять свое приветствие этим API, поэтому я действительно не хочу запрашивать каждое нажатие клавиши любым из моих пользователей. Функция debounced ограничивает частоту вызовов функции. Самое большее, это приложение будет делать запрос к API раз в полсекунды.

Время восхода солнца (реагировать)

Вот Javascript для версии приложения React. Опять же, вы можете увидеть HTML и CSS в демо-версии Codepen, встроенной ниже, если вам интересно.

function isNumeric(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}
function debounced(delay, fn) {
  let timerId;
  return function(...args) {
    if (timerId) {
      clearTimeout(timerId);
    }
    timerId = setTimeout(() => {
      fn(...args);
      timerId = null;
    }, delay);
  };
}
class CoordinatesForm extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <form>
        <LatField
          updateLat={newLat => {
            this.props.updateCoords({ lat: newLat });
          }}
          lat={this.props.lat}
        />
        <LongField
          updateLong={newLong => {
            this.props.updateCoords({ long: newLong });
          }}
          long={this.props.long}
        />
      </form>
    );
  }
}
class LatField extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      lat: this.props.lat
    };
  }
  componentDidUpdate(prevProps) {
    const lat = this.props.lat;
    if (lat !== prevProps.lat) {
      this.setState({ lat });
    }
  }
  updateLat = event => {
    let newLat = event.target.value;
    if (!newLat) {
      this.setState({ lat: 0 });
      return this.props.updateLat(0);
    }
    if (isNumeric(newLat)) {
      newLat = parseFloat(newLat);
    } else if (newLat !== "-") {
      return;
    }
    this.setState({ lat: newLat });
    this.props.updateLat(newLat);
  };
  render() {
    return (
      <label for="">
        lat:
        <input
          type="text"
          id="lat"
          value={this.state.lat}
          onChange={this.updateLat}
        />
      </label>
    );
  }
}
class LongField extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      long: this.props.long
    };
  }
  componentDidUpdate(prevProps) {
    const long = this.props.long;
    if (long !== prevProps.long) {
      this.setState({ long });
    }
  }
  updateLong = event => {
    let newLong = event.target.value;
    if (!newLong) {
      this.setState({ long: 0 });
      return this.props.updateLong(0);
    }
    if (isNumeric(newLong)) {
      newLong = parseFloat(newLong);
    } else if (newLong !== "-") {
      return;
    }
    this.setState({ long: newLong });
    this.props.updateLong(newLong);
  };
  render() {
    return (
      <label for="">
        long:
        <input
          type="text"
          id="long"
          value={this.state.long}
          onChange={this.updateLong}
        />
      </label>
    );
  }
}
class TimesDisplay extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      sunrise: "Unknown",
      sunset: "Unknown"
    };
  }
  componentDidUpdate(prevProps) {
    const lat = this.props.lat;
    const long = this.props.long;
    if (lat !== prevProps.lat || long !== prevProps.long) {
      this.updateTimesDebounced(lat, long);
    }
  }
  updateTimes = (lat, long) => {
    if (isNumeric(lat) && isNumeric(long)) {
      return fetch(`https://api.sunrise-sunset.org/json?lat=${lat}&lng=${long}`)
        .then(response => {
          if (!response.ok) {
            this.setState({
              sunrise: "Invalid",
              sunset: "Invalid"
            });
            throw Error(`${response.status} response on times request`);
          }
          return response.json();
        })
        .then(data => {
          this.setState({
            sunrise: data.results.sunrise,
            sunset: data.results.sunset
          });
        })
      .catch(error => {
        console.error(error.message);
      });
    }
  }
  updateTimesDebounced = debounced(500, this.updateTimes)
  render() {
    return (
      <div>
        <SunriseTime time={this.state.sunrise} />
        <SunsetTime time={this.state.sunset} />
      </div>
    );
  }
}
class SunriseTime extends React.Component {
  render() {
    return <div class="sunrise">Sunrise: {this.props.time}</div>;
  }
}
class SunsetTime extends React.Component {
  render() {
    return <div class="sunset">Sunset: {this.props.time}</div>;
  }
}
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      lat: 0,
      long: 0
    };
  }
  updateCoords = updateObject => {
    this.setState(updateObject);
  };
  componentDidMount() {
    navigator.geolocation.getCurrentPosition(position => {
      const lat = position.coords.latitude;
      const long = position.coords.longitude;
      this.setState({ lat, long });
    }, error => {
      console.error('Couldn\'t get your current position from the browser');
    });
  }
  render() {
    return (
      <div>
        <CoordinatesForm
          lat={this.state.lat}
          long={this.state.long}
          updateCoords={this.updateCoords}
        />
        <TimesDisplay lat={this.state.lat} long={this.state.long} />
      </div>
    );
  }
}
ReactDOM.render(<App />, document.querySelector(".app"));

См. Pen Sunrise app (вариант React) Девона Кэмпбелла (@raddevon) на CodePen.

Я фанат React, но я с готовностью признаю, что был шокирован тем, сколько кода потребовалось для этого в React. По большей части это связано с тем, что я все разложил на компоненты, и мне приходится делиться состоянием между компонентами. Вот что я имею в виду.

Есть внешний компонент, который я называю «Приложение», который содержит все остальное и сохраняет состояние, используемое всеми другими компонентами. Внутри компонента приложения есть два компонента: «CoordinatesForm» и «TimesDisplay». Каждый из них имеет по два компонента, каждый из которых соответствует каждому из двух значений (широта/долгота и восход/закат солнца соответственно). Пользователь внесет изменения, например, в поле «широта». Это изменение должно быть отражено в состоянии приложения, поскольку компоненты не могут легко обмениваться данными, кроме как через общего предка.

Это означает, что я передаю состояние вверх и вниз по дереву компонентов. В React, чтобы установить состояние родителя, мне нужно создать метод для родительского компонента, который устанавливает его состояние. Затем я могу передать это дочерним элементам через реквизиты (имя React для данных, передаваемых в дочерние компоненты). Дети вызывают этот метод и передают данные, необходимые им для перехода в состояние предка.

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

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

Еще немного контекста:

  • HTML-документ в этой версии очень скудный. Это связано с тем, что большая часть страницы обрабатывается React по мере обработки компонентов. HTML нужен был только элемент для отображения приложения React.
  • React, похоже, не хотел взаимодействовать с числовым вводом, который я использовал в другом приложении, поэтому я решил использовать здесь стандартный текстовый ввод с некоторой проверкой. Эта проверка также добавила немного кода. Вероятно, есть способ улучшить работу ввода чисел, но приложение и так работает нормально.

Итог: почему вы должны заботиться

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

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

Первоначально опубликовано на raddevon.com.