Estado ReactJS vs prop

121

Isso pode estar trilhando essa linha entre respondente e opinativo, mas eu estou indo e voltando sobre como estruturar um componente ReactJS à medida que a complexidade cresce e pode usar alguma direção.

Vindo do AngularJS, quero passar meu modelo para o componente como uma propriedade e fazer com que o componente modifique o modelo diretamente. Ou devo dividir o modelo em várias statepropriedades e compilá-lo novamente ao enviá-lo de volta? Qual é a maneira ReactJS?

Veja o exemplo de um editor de postagem no blog. Tentar modificar o modelo diretamente acaba parecendo:

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>
    );
  }
});

O que parece errado.

É mais a maneira React de criar nossa textpropriedade de modelo statee compilá-la novamente no modelo antes de salvar como:

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>
    );
  }
});

Isso não requer uma chamada this.forceUpdate(), mas à medida que o modelo cresce, (uma postagem pode ter um autor, assunto, tags, comentários, classificações, etc ...) o componente começa a ficar realmente complicado.

O primeiro método com o ReactLink é o caminho a seguir?

nicholas
fonte

Respostas:

64

Sua segunda abordagem é mais parecida. O React não se importa tanto com os modelos quanto com os valores e com a forma como eles fluem pelo aplicativo. Idealmente, seu modelo de postagem seria armazenado em um único componente na raiz. Em seguida, você cria componentes filhos que cada um consome partes do modelo.

Você pode passar retornos de chamada para os filhos que precisam modificar seus dados e chamá-los do componente filho.

Modificar this.props ou this.state diretamente não é uma boa ideia, porque o React não poderá acompanhar as alterações. Isso ocorre porque o React faz uma comparação superficial do seu suporte de postagem para determinar se ele foi alterado.

Eu criei esse jsfiddle para mostrar como os dados poderiam fluir de um componente externo para um componente interno.

O handleClickmétodo mostra três maneiras de (im) atualizar corretamente o estado:

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
fonte
Mas o que fazemos se tivermos um modelo opaco, com suas próprias funções de manipulação de estado? Por exemplo, suponha que, em vez de um textcampo, tenhamos um setText método que faça validação e outras coisas. Eu posso ver o método (2) funcionando se setTextfor puro e retornar uma nova instância do modelo. No entanto, se setTextapenas atualizar o estado interno, ainda precisaríamos chamar forceUpdate, certo?
Hugomg
1
Sim, você pode ligar forceUpdate, mas nesse ponto você está "vazando" do React. Talvez seja melhor passar um setState()retorno de chamada para o modelo opaco para evitar ter que disparar manualmente as renderizações novamente.
XGJ
Ainda não tenho certeza se estou entendendo completamente. Portanto, qualquer componente destinado a modificar dados precisa fazer uma cópia profunda dos objetos passados? Em seguida, modifique e envie a cópia a montante, para não modificar os dados originais? Eventualmente, a mudança fluirá até a raiz, onde é tratada, e todo o aplicativo será renderizado novamente? Isso está certo?
Nicholas
97

Atualização de 2016: a reação foi alterada e a explicação "props vs state" se tornou muito simples. Se um componente precisar alterar dados - coloque-o em um estado, caso contrário, em adereços. Porque adereços são somente leitura agora.

Qual é a diferença exata entre adereços e estado?

Você pode encontrar uma boa explicação aqui (versão completa)

Alterar adereços e estado

mrvol
fonte
1
na verdade SetProps () poderia alterar suportes no interior de um componente e desencadear uma re-render
WaiKit Kung
2
setPropsestá obsoleto e não deve ser usado. Substituição é renderizar novamente o componente e deixar o React lidar com as diferenças.
Jdmichal 04/04
E se você está procurando um vídeo explicador: youtube.com/watch?v=qh3dYM6Keuw
jaisonDavis
35

Do documento React

adereços são imutáveis: eles são passados ​​dos pais e são "de propriedade" dos pais. Para implementar interações, introduzimos um estado mutável no componente. this.state é privado para o componente e pode ser alterado chamando this.setState (). Quando o estado é atualizado, o componente é renderizado novamente.

No TrySpace : quando props (ou state) são atualizados (via setProps / setState ou parent), o componente é renderizado novamente.

Fizer Khan
fonte
16

Uma leitura de Thinking in React :

Vamos examinar cada um e descobrir qual é o estado. Basta fazer três perguntas sobre cada parte dos dados:

  1. É passado de um pai através de adereços? Se assim for, provavelmente não é estado.
  2. Isso muda com o tempo? Caso contrário, provavelmente não é estado.

  3. Você pode calculá-lo com base em qualquer outro estado ou adereços no seu componente? Se assim for, não é estado.

onmyway133
fonte
13

Não tenho certeza se estou respondendo à sua pergunta, mas descobri que, especialmente em um aplicativo grande / crescente, o padrão Container / Component funciona incrivelmente bem.

Essencialmente, você tem dois componentes do React:

  • um componente de exibição "puro", que trata de estilo e interação DOM;
  • um componente de contêiner, que lida com acessar / salvar dados externos, gerenciar estado e renderizar o componente de exibição.

Exemplo

NB Este exemplo é provavelmente muito simples para ilustrar os benefícios desse padrão, pois é bastante detalhado para um caso tão simples.

/**
 * 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>
    );
  }
});

Benefícios

Ao manter a lógica de exibição e o gerenciamento de dados / estado separados, você tem um componente de exibição reutilizável que:

  • pode ser facilmente iterado com diferentes conjuntos de adereços usando algo como reat-component-playground
  • pode ser embrulhado com um contêiner diferente para um comportamento diferente (ou combinar com outros componentes para criar partes maiores do seu aplicativo

Você também tem um componente de contêiner que lida com toda a comunicação externa. Isso deve facilitar a flexibilidade da maneira como você acessa seus dados, se você fizer alterações sérias mais tarde *.

Esse padrão também torna a escrita e a implementação de testes de unidade muito mais diretas.

Depois de iterar um grande aplicativo React algumas vezes, descobri que esse padrão mantém as coisas relativamente indolor, especialmente quando você tem componentes maiores com estilos calculados ou interações DOM complicadas.

* Leia o padrão de fluxo e dê uma olhada no Marty.js , que inspirou amplamente essa resposta (e eu tenho usado muito ultimamente) Redux (e react-redux ), que implementa esse padrão extremamente bem.

Nota para quem lê isso em 2018 ou mais tarde:

O React evoluiu bastante desde que esta resposta foi escrita, especialmente com a introdução de Hooks . No entanto, a lógica de gerenciamento de estado subjacente deste exemplo permanece a mesma e, mais importante, os benefícios que você obtém ao manter sua lógica de estado e de apresentação separada ainda se aplicam da mesma maneira.

Jim O'Brien
fonte
0

Eu acho que você está usando um anti-padrão que o Facebook já explicou neste link

Aqui está o que você está encontrando:

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>
    );
  }
});

A primeira vez que o componente interno for renderizado, ele terá {foo: 'bar'} como o valor prop. Se o usuário clicar na âncora, o estado do componente pai será atualizado para {value: {foo: 'barbar'}}, acionando o processo de re-renderização do componente interno, que receberá {foo: 'barbar'} como o novo valor para o suporte.

O problema é que, como os componentes pai e interno compartilham uma referência ao mesmo objeto, quando o objeto é alterado na linha 2 da função onClick, o suporte do componente interno será alterado. Portanto, quando o processo de nova renderização for iniciado e o invocadorComponentUpdate for invocado, this.props.value.foo será igual a nextProps.value.foo, porque, na verdade, this.props.value faz referência ao mesmo objeto que nextProps.value.

Consequentemente, como perderemos a alteração no suporte e curto-circuito no processo de nova renderização, a interface do usuário não será atualizada de 'bar' para 'barbar'

Alex Nguyen
fonte
Você também pode postar o Innercomponentscódigo?
Abdullah Khan