ReactJS state vs prop

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 не столько заботится о моделях, сколько о ценностях и о том, как они проходят через ваше приложение. В идеале ваша пост-модель должна храниться в одном корневом компоненте. Затем вы создаете дочерние компоненты, каждый из которых потребляет части модели.

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

Непосредственное изменение this.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} />;
  }
});
JXG
источник
Но что нам делать, если у нас есть непрозрачная модель со своими собственными функциями управления состоянием? Например, предположим, что вместо textполя у нас есть setText метод, который выполняет проверку и некоторые другие вещи. Я вижу, что метод (2) работает, если setTextон чистый и возвращает новый экземпляр модели. Однако, если бы мы setTextпросто обновили внутреннее состояние, нам все равно нужно было бы вызвать forceUpdate, верно?
hugomg
1
Да, вы можете позвонить forceUpdate, но в этот момент вы «утекаете» из React. Возможно, лучше передать setState()обратный вызов непрозрачной модели, чтобы избежать необходимости вручную запускать повторный рендеринг.
jxg 07
Я все еще не уверен, что полностью понимаю. Значит, любой компонент, предназначенный для изменения данных, должен делать глубокую копию переданных свойств? Затем изменить и отправить эту копию вверх по течению, чтобы не изменять исходные данные? В конце концов, изменение перейдет в корень, где оно будет обработано, и все приложение будет повторно отрисовано? Это правильно?
Николай
97

Обновление 2016: React изменен, и объяснение «props vs state» стало очень простым. Если компоненту нужно изменить данные - поместите его в состояние, иначе в props. Потому что теперь реквизиты доступны только для чтения .

В чем точная разница между реквизитом и состоянием?

Вы можете найти хорошее объяснение здесь (полная версия)

Изменение реквизита и состояния

mrvol
источник
1
на самом деле setProps () может изменять свойства внутри компонента и запускать повторный рендеринг
WaiKit Kung
2
setPropsустарела и не должна использоваться. Замена заключается в том, чтобы повторно отрендерить компонент и позволить React обработать различия.
jdmichal
И если вы ищете поясняющее видео: youtube.com/watch?v=qh3dYM6Keuw
jaisonDavis
35

Из документа React

props неизменяемы: они передаются от родителя и «принадлежат» ему. Чтобы реализовать взаимодействия, мы вводим в компонент изменяемое состояние. this.state является частным для компонента и может быть изменено путем вызова this.setState (). Когда состояние обновляется, компонент повторно отображает себя.

Из TrySpace : когда реквизиты (или состояние) обновляются (через setProps / setState или parent), компонент также перерисовывается.

Физер Хан
источник
16

Чтение из книги Thinking in React :

Давайте рассмотрим каждый из них и выясним, какой из них является государством. Просто задайте три вопроса по каждому фрагменту данных:

  1. Он передается от родителя через реквизит? Если так, вероятно, это не состояние.
  2. Меняется ли это со временем? Если нет, вероятно, это не состояние.

  3. Можете ли вы вычислить его на основе любого другого состояния или свойств вашего компонента? Если так, то это не состояние.

onmyway133
источник
13

Я не уверен, отвечу ли я на ваш вопрос, но я обнаружил, что, особенно в большом / растущем приложении, шаблон "Контейнер / Компонент" работает невероятно хорошо.

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

  • «чистый» компонент отображения, который занимается стилизацией и взаимодействием с DOM;
  • компонент контейнера, который имеет дело с доступом / сохранением внешних данных, управлением состоянием и отображением компонента отображения.

пример

NB. Этот пример, вероятно, слишком прост, чтобы проиллюстрировать преимущества этого шаблона, поскольку он довольно многословен для такого простого случая.

/**
 * 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-component-plays
  • можно обернуть другим контейнером для другого поведения (или объединить с другими компонентами для создания более крупных частей вашего приложения.

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

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

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

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

Примечание для тех, кто читает это в 2018 году или позже:

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

Джим О'Брайен
источник
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. Если пользователь нажимает на якорь, состояние родительского компонента будет обновлено до {value: {foo: 'barbar'}}, запустив процесс повторной визуализации внутреннего компонента, который получит {foo: 'barbar'} как новое значение для опоры.

Проблема в том, что, поскольку родительский и внутренний компоненты используют ссылку на один и тот же объект, когда объект изменяется в строке 2 функции onClick, опора внутреннего компонента изменится. Итак, когда начинается процесс повторного рендеринга и вызывается shouldComponentUpdate, this.props.value.foo будет равно nextProps.value.foo, потому что на самом деле this.props.value ссылается на тот же объект, что и nextProps.value.

Следовательно, поскольку мы пропустим изменение в реквизите и закроем процесс повторного рендеринга, пользовательский интерфейс не будет обновляться с 'bar' на 'barbar'.

Алекс Нгуен
источник
Не могли бы вы также выложить Innercomponentsкод?
Абдулла Хан