кодесурса
«Реагировать

Поднятие вверх

script1adsense2code
script1adsense3code

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

В этом уроке мы создадим калькулятор температуры, который вычисляет, будет ли вода кипеть при данной температуре.

Мы начнем с компонента BoilingVerdict. Он принимает температуру по Цельсию в качестве реквизита и печатает, достаточно ли этого для кипячения воды:

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return 

The water would boil.

; } return

The water would not boil.

; }

Далее мы создадим компонент под названием Калькулятор. Он отображает <input>, который позволяет вам ввести температуру, и сохраняет ее значение в this.state.temperam.

Кроме того, он отображает BoilingVerdict для текущего входного значения.

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }
  handleChange(e) {
    this.setState({temperature: e.target.value});
  }
  render() {
    const temperature = this.state.temperature;
    return (
      <fieldset>
        <legend>Enter temperature in Celsius:</legend>
        <input
          value={temperature}
          onChange={this.handleChange} />
        <BoilingVerdict
          celsius={parseFloat(temperature)} />
      </fieldset>
    );
  }
}

Добавление второго входа

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

Мы можем начать с извлечения компонента TemperatureInput из калькулятора. Мы добавим в него новую шкалу, которая может быть «c» или «f»:

const scaleNames = {
  c: 'Celsius',
  f: 'Fahrenheit'
};
class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }
  handleChange(e) {
    this.setState({temperature: e.target.value});
  }
  render() {
    const temperature = this.state.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

Теперь мы можем изменить калькулятор для отображения двух отдельных входов температуры:

class Calculator extends React.Component {
  render() {
    return (
      <div>
        <TemperatureInput scale="c" />
        <TemperatureInput scale="f" />
      </div>
    );
  }
}

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

Мы также не можем отобразить BoilingVerdict из калькулятора. Калькулятор не знает текущую температуру, потому что он скрыт внутри TemperatureInput.

Написание функций преобразования

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

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}

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

Он возвращает пустую строку при недопустимой температуре и сохраняет результат округленным до третьего десятичного знака:

function tryConvert(temperature, convert) {
  const input = parseFloat(temperature);
  if (Number.isNaN(input)) {
    return '';
  }
  const output = convert(input);
  const rounded = Math.round(output * 1000) / 1000;
  return rounded.toString();
}

Например, tryConvert ('abc', toCelsius) возвращает пустую строку, а tryConvert ('10 .22 ', toFahrenheit) возвращает '50 .396'.

Поднятие вверх

В настоящее время оба компонента TemperatureInput независимо хранят свои значения в локальном состоянии:

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }
  handleChange(e) {
    this.setState({temperature: e.target.value});
  }
  render() {
    const temperature = this.state.temperature;
    // ...  

Однако мы хотим, чтобы эти два входа были синхронизированы друг с другом. Когда мы обновляем ввод по Цельсию, ввод по Фаренгейту должен отражать преобразованную температуру, и наоборот.

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

Если калькулятор владеет общим состоянием, он становится «источником правды» для текущей температуры в обоих входах. Он может проинструктировать их обоих иметь значения, которые соответствуют друг другу. Поскольку реквизиты обоих компонентов TemperatureInput поступают от одного и того же родительского компонента Calculator, оба входа всегда будут синхронизированы.

Давайте посмотрим, как это работает, шаг за шагом.

Во-первых, мы заменим this.state.tength на this.props.tempera в компоненте TemperatureInput. А пока давайте представим, что this.props.tempera уже существует, хотя в будущем нам нужно будет передать его из Калькулятора:

render() {
    // Before: const temperature = this.state.temperature;
    const temperature = this.props.temperature;
    // ...

Мы знаем, что реквизит только для чтения. Когда температура находилась в локальном состоянии, TemperatureInput мог просто вызвать this.setState (), чтобы изменить его. Однако теперь, когда температура поступает от родителя в качестве опоры, TemperatureInput не может ее контролировать.

В React это обычно решается созданием компонента "контролируемым". Точно так же, как DOM <input> принимает значение и свойство onChange, так и пользовательский TemperatureInput может принимать значения температуры и onTemperaChange из своего родительского калькулятора.

Теперь, когда TemperatureInput хочет обновить свою температуру, он вызывает this.props.onTemperaChange:

handleChange(e) {
    // Before: this.setState({temperature: e.target.value});
    this.props.onTemperatureChange(e.target.value);
    // ...

Замечания:

В пользовательских компонентах нет особого значения ни для температуры, ни для свойства onTemperaChange. Мы могли бы назвать их как угодно, например, назвать их value и onChange, что является общим соглашением.

Пропуск onTemperaChange будет предоставлен вместе с пропеллером температуры родительским компонентом Calculator. Он будет обрабатывать изменения, изменяя свое собственное локальное состояние, таким образом повторно отображая оба входа с новыми значениями. Очень скоро мы рассмотрим новую реализацию калькулятора.

Прежде чем углубляться в изменения в калькуляторе, давайте вспомним наши изменения в компоненте TemperatureInput. Мы удалили из него локальное состояние, и вместо того, чтобы читать this.state.temperam, мы теперь читаем this.props.temperars. Вместо вызова this.setState (), когда мы хотим внести изменение, мы теперь вызываем this.props.onTemperaChange (), который будет предоставлен калькулятором:

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange(e) {
    this.props.onTemperatureChange(e.target.value);
  }
  render() {
    const temperature = this.props.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

Теперь давайте обратимся к компоненту Калькулятор.

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

Например, если мы введем значение 37 в поле Цельсия, состояние компонента «Калькулятор» будет следующим:

{
  temperature: '37',
  scale: 'c'
}

Если позже мы изменим поле Фаренгейта на 212, состояние Калькулятора будет следующим:

{
  temperature: '212',
  scale: 'f'
}

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

Входные данные остаются синхронизированными, потому что их значения вычисляются из одного и того же состояния:

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
    this.state = {temperature: '', scale: 'c'};
  }
  handleCelsiusChange(temperature) {
    this.setState({scale: 'c', temperature});
  }
  handleFahrenheitChange(temperature) {
    this.setState({scale: 'f', temperature});
  }
  render() {
    const scale = this.state.scale;
    const temperature = this.state.temperature;
    const celsius = scale ==='f' ? tryConvert(temperature, toCelsius) : temperature;
    const fahrenheit = scale ==='c' ? tryConvert(temperature, toFahrenheit) : temperature;
    return (
      <div>
        <TemperatureInput
          scale="c"
          temperature={celsius}
          onTemperatureChange={this.handleCelsiusChange} />
       <TemperatureInput
          scale="f"
          temperature={fahrenheit}
          onTemperatureChange={this.handleFahrenheitChange} />
        <BoilingVerdict
          celsius={parseFloat(celsius)} />
     </div>
    );
  }
}

Теперь, независимо от того, какой ввод вы редактируете, this.state.tempera и this.state.scale в калькуляторе обновляются. Один из входных данных получает значение как есть, поэтому любой пользовательский ввод сохраняется, а другое входное значение всегда пересчитывается на основе этого.

Новый контент: Composer: менеджер зависимостей для PHP , R программирования


script1adsense4code
script1adsense5code
disqus2code
script1adsense6code
script1adsense7code
script1adsense8code
buysellads2code