Стан ReactJS проти опори


121

Це може бути вподобання цієї межі між відповідальним та впевненим, але я рухаюсь вперед і назад щодо того, як структурувати компонент ReactJS, коли складність зростає і може використовувати певний напрямок.

Починаючи з AngularJS, я хочу передати свою модель в компонент як властивість і змусити компонент змінити модель безпосередньо. Або я повинен розбивати модель на різні stateвластивості та компілювати її разом, коли надсилаю назад вгору? Який спосіб ReactJS?

Візьмемо приклад редактора публікацій в блозі. Спроба модифікувати модель безпосередньо виглядає так:

var PostEditor = React.createClass({
  updateText: function(e) {
    var text = e.target.value;
    this.props.post.text = text;
    this.forceUpdate();
  },
  render: function() {
    return (
      <input value={this.props.post.text} onChange={this.updateText}/>
      <button onClick={this.props.post.save}/>Save</button>
    );
  }
});

Що здається неправильним.

Чи це більше спосіб React зробити textвластивість нашої моделі stateта скомпілювати її назад в модель перед збереженням:

var PostEditor = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },
  componentWillMount: function() {
    this.setState({
      text: this.props.post.text
    });
  },
  updateText: function(e) {
    this.setState({
      text: e.target.value
    });
  },
  savePost: function() {
    this.props.post.text = this.state.text;
    this.props.post.save();
  },
  render: function() {
    return (
      <input value={this.state.text} onChange={this.updateText}/>
      <button onClick={this.savePost}/>Save</button>
    );
  }
});

Це не вимагає дзвінка this.forceUpdate(), але в міру зростання моделі (у публікації може бути автор, тема, теги, коментарі, рейтинги тощо) компонент починає ускладнюватися.

Перший спосіб з ReactLink - це шлях?

Відповіді:


64

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

Ви можете передавати зворотні дзвінки дітям, яким потрібно змінити ваші дані, та зателефонувати їм із дочірнього компонента.

Змінення цього.props або this.state безпосередньо не є гарною ідеєю, тому що React не зможе взяти на себе зміни. Це тому, що React робить неглибоке порівняння вашої публікації, щоб визначити, чи змінилося воно.

Я зробив цю jsfiddle, щоб показати, як дані можуть перетікати від зовнішнього до внутрішнього компонента.

У handleClickметоді показує 3 спосіб (їм) належним чином стан поновлення:

var Outer = React.createClass({

  getInitialState: function() {
    return {data: {value: 'at first, it works'}};
  },

  handleClick: function () {

    // 1. This doesn't work, render is not triggered.
    // Never set state directly because the updated values
    // can still be read, which can lead to unexpected behavior.

    this.state.data.value = 'but React will never know!';

    // 2. This works, because we use setState

    var newData = {value: 'it works 2'};
    this.setState({data: newData});

    // 3. Alternatively you can use React's immutability helpers
    // to update more complex models.
    // Read more: http://facebook.github.io/react/docs/update.html

    var newState = React.addons.update(this.state, {
      data: {value: {$set: 'it works'}}
    });
    this.setState(newState);
 },

  render: function() {
      return <Inner data={this.state.data} handleClick={this.handleClick} />;
  }
});

Але що робити, якщо ми маємо непрозору модель із власними функціями маніпулювання державою? Наприклад, припустимо, що замість textполя у нас є setText метод, який робить перевірку та деякі інші речі. Я бачу метод (2), який працює, якщо setTextвін чистий, і повертає абсолютно новий екземпляр моделі. Однак, якщо setTextпросто оновити внутрішній стан, нам все-таки потрібно зателефонувати forceUpdate, правда?
хугомг

1
Так, ви можете зателефонувати forceUpdate, але в цей момент ви "витікаєте" з React. Можливо, буде краще передати setState()зворотний виклик непрозорій моделі, щоб уникнути необхідності вручну викликати повторні рендери.
jxg

Я досі не впевнений, що цілком розумію. Отже, будь-який компонент, призначений для зміни даних, повинен зробити глибоку копію переданого реквізиту? Потім змінити та надіслати цю копію вище, щоб не змінити вихідні дані? Врешті-решт зміна проллється аж до кореня, де вона розглядається, і весь додаток буде видано? Це так?
ніколас

97

Оновлення 2016 року: React змінено, і пояснення "реквізит проти стану" стало дуже простим. Якщо компонент потребує зміни даних - переведіть його в стан, інакше в реквізити. Тому що реквізит читається лише зараз.

Яка точна різниця між реквізитом і станом?

Ви можете знайти хороше пояснення тут (повна версія)

Зміна реквізиту та штату


1
насправді setProps () може змінити реквізит всередині компонента і викликати повторне відображення
WaiKit Kung

2
setPropsзастаріла і не повинна використовуватися. Заміна полягає в тому, щоб повторно віддати компонент і дозволити React вирішувати відмінності.
jdmichal

І якщо ви шукаєте відео для пояснювачів: youtube.com/watch?v=qh3dYM6Keuw
jaisonDavis

35

Від React doc

реквізит незмінний: вони передаються від батьківських і перебувають у "власності" батьків. Для реалізації взаємодій ми вводимо змінний стан до компонента. this.state є приватним для компонента і його можна змінити, зателефонувавши до цього.setState (). Коли стан оновлюється, компонент відновлює себе.

З TrySpace : коли реквізити (або стан) оновлюються (через setProps / setState або батьківський), компонент також повторно відображається.


16

Читання з Мислення в реагуванні :

Давайте переглянемо кожну з них і з’ясуємо, яка з них є державою. Просто задайте три запитання щодо кожної інформації:

  1. Це передається від батьків через реквізит? Якщо так, то, ймовірно, це не стан.
  2. Чи змінюється воно з часом? Якщо ні, то, ймовірно, це не стан.

  3. Чи можете ви обчислити його на основі будь-якого іншого стану або реквізиту вашого компонента? Якщо так, це не держава.


13

Я не впевнений, чи відповідаю я на ваше запитання, але я виявив, що, особливо у великій / зростаючій програмі, модель Container / Component працює надзвичайно добре.

По суті, у вас є два компоненти React:

  • "чистий" компонент дисплея, який стосується стилізації та взаємодії DOM;
  • компонент контейнера, який займається доступом / збереженням зовнішніх даних, керуванням станом та наданням компонента відображення.

Приклад

Примітка. Цей приклад, мабуть, занадто простий для того, щоб проілюструвати переваги цієї закономірності, оскільки це досить багатослівний для такого прямого випадку.

/**
 * Container Component
 *
 *  - Manages component state
 *  - Does plumbing of data fetching/saving
 */

var PostEditorContainer = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },

  componentWillMount: function() {
    this.setState({
      text: getPostText()
    });
  },

  updateText: function(text) {
    this.setState({
      text: text
    });
  },

  savePost: function() {
    savePostText(this.state.text);
  },

  render: function() {
    return (
      <PostEditor
        text={this.state.text}
        onChange={this.updateText.bind(this)}
        onSave={this.savePost.bind(this)}
      />
    );
  }
});


/**
 * Pure Display Component
 *
 *  - Calculates styling based on passed properties
 *  - Often just a render method
 *  - Uses methods passed in from container to announce changes
 */

var PostEditor = React.createClass({
  render: function() {
    return (
      <div>
        <input type="text" value={this.props.text} onChange={this.props.onChange} />
        <button type="button" onClick={this.props.onSave} />
      </div>
    );
  }
});

Переваги

Заохочуючи логіку відображення та управління даними / станом окремо, ви маєте повторно використаний компонент дисплея, який:

  • можна легко повторити з різними наборами реквізиту, використовуючи щось подібне до ігрового майданчика для реагування
  • можна обернути іншим контейнером для різної поведінки (або комбінувати з іншими компонентами, щоб створити більші частини вашої програми

У вас також є компонент контейнера, який стосується всього зовнішнього зв'язку. Це повинно полегшити гнучкість щодо доступу до ваших даних, якщо пізніше ви внесете якісь серйозні зміни *.

Ця модель також робить написання та реалізацію одиничних тестів набагато простішими.

Кілька разів переглянувши велику програму React, я виявив, що ця модель зберігає речі відносно безболісно, ​​особливо коли у вас є більші компоненти з обчисленими стилями або складними взаємодіями DOM.

* Прочитайте схему потоку та погляньте на Marty.js , який багато в чому надихнув цю відповідь (а я останнім часом багато використовую) Reduxреагую-редукс ), які надзвичайно добре реалізують цю схему.

Примітка для тих, хто читає це у 2018 році чи пізніше:

З моменту написання цієї відповіді Реакт розвинувся досить сильно, особливо з введенням Гаків . Однак основна логіка управління державою на цьому прикладі залишається тією ж, і що ще важливіше, переваги, які ви отримуєте від збереження вашої стану та логіки презентації, залишаються однаковими.


0

Я думаю, ви використовуєте антидіапазон, який Facebook вже пояснив за цим посиланням

Ось що ви знайдете:

React.createClass({
  getInitialState: function() {
    return { value: { foo: 'bar' } };
  },

  onClick: function() {
    var value = this.state.value;
    value.foo += 'bar'; // ANTI-PATTERN!
    this.setState({ value: value });
  },

  render: function() {
    return (
      <div>
        <InnerComponent value={this.state.value} />
        <a onClick={this.onClick}>Click me</a>
      </div>
    );
  }
});

Перший раз, коли внутрішній компонент буде виведений, він матиме {foo: 'bar'} як опора значення. Якщо користувач натисне на якір, стан батьківського компонента буде оновлений до {value: {foo: 'barbar'}}, запустивши процес повторного візуалізації внутрішнього компонента, який отримає {foo: 'barbar'} як нове значення для опори

Проблема полягає в тому, що оскільки батьківський і внутрішній компоненти поділяють посилання на один і той же об'єкт, коли об'єкт мутується в рядку 2 функції onClick, опора внутрішнього компонента зміниться. Отже, коли запускається процес повторного візуалізації, і в ньому повинно викликатися ShouldComponentUpdate, цей.props.value.foo буде дорівнює nextProps.value.foo, тому що насправді цей.props.value посилається на той самий об'єкт, що і nextProps.value.

Отже, оскільки ми пропустимо зміну опори та короткого замикання в процесі повторного візуалізації, інтерфейс не буде оновлений з 'bar' на 'barbar'


Чи можете ви також опублікувати Innercomponentsкод?
Абдулла хан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.