Как сохранить состояние компонента React между mount/unmount?

У меня есть простой компонент <StatefulView>, который поддерживает внутреннее состояние. У меня есть другой компонент <App>, который переключает отображение или нет <StatefulView>.

Однако я хочу сохранить внутреннее состояние <StatefulView> между установкой/отключением.

Я решил, что могу создать экземпляр компонента в <App>, а затем управлять его визуализацией/установкой.

var StatefulView = React.createClass({
  getInitialState: function() {
    return {
      count: 0
    }
  },
  inc: function() {
    this.setState({count: this.state.count+1})
  },
  render: function() {
    return (
        <div>
          <button onClick={this.inc}>inc</button>
          <div>count:{this.state.count}</div>
        </div>
    )
  }
});

var App = React.createClass({
  getInitialState: function() {
    return {
      show: true,
      component: <StatefulView/>
    }
  },
  toggle: function() {
    this.setState({show: !this.state.show})
  },
  render: function() {
    var content = this.state.show ? this.state.component : false
    return (
      <div>
        <button onClick={this.toggle}>toggle</button>
        {content}
      </div>
    )
  }
});

Это, по-видимому, не работает, и при каждом переключении создается новый <StatefulView>.

Здесь JSFiddle.

Есть ли способ повесить на тот же компонент после его размонтирования, чтобы его можно было снова установить?

Ответы

Ответ 1

Я тоже немного подумал об этом.

Я положил свои попытки вместе в этом репо: реакция-держать-состояние.

Для меня наиболее подходящим решением было следующее:

import React, { Component, PropTypes } from 'react';

// Set initial state
let state = { counter: 5 };

class Counter extends Component {

  constructor(props) {
    super(props);

    // Retrieve the last state
    this.state = state;

    this.onClick = this.onClick.bind(this);
  }

  componentWillUnmount() {
    // Remember state for the next mount
    state = this.state;
  }

  onClick(e) {
    e.preventDefault();
    this.setState(prev => ({ counter: prev.counter + 1 }));
  }

  render() {
    return (
      <div>
        <span>{ this.state.counter }</span>
        <button onClick={this.onClick}>Increase</button>
      </div>
    );
  }
}

export default Counter;

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

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

Ответ 2

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

1) Наиболее очевидное место для сохранения состояния находится в родительском компоненте.

Это не вариант для меня, потому что я пытаюсь нажимать и всплывать из объекта UINavigationController.

2) Вы можете сохранить состояние в другом месте, например, в хранилище Flux или в каком-либо глобальном объекте.

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

3) Передайте измененный объект для сохранения и восстановления состояния.

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

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

function createInstance() {
  var func = null;
  return {
    save: function(f) {
      func = f;
    },
    restore: function(context) {
      func && func(context);
    }
  }
}

Теперь в getInitialState я создаю новый экземпляр для компонента:

component: <StatefulView instance={createInstance()}/>

Затем в StatefulView мне просто нужно сохранить и восстановить в componentWillMount и componentWillUnmount.

componentWillMount: function() {
  this.props.instance.restore(this)
},
componentWillUnmount: function() {
  var state = this.state
  this.props.instance.save(function(ctx){
    ctx.setState(state)
  })
}

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

Ответ 3

Хм, вы можете использовать localStorage или AsyncStorage если React Native.

Реагировать на веб

componentWillUnmount() {
  localStorage.setItem('someSavedState', JSON.stringify(this.state))
}

Затем в тот же день или через 2 секунды:

componentWillMount() {
  const rehydrate = JSON.parse(localStorage.getItem('someSavedState'))
  this.setState(rehydrate)
}

React Native

import { AsyncStorage } from 'react-native'

async componentWillMount() {
  try {
    const result = await AsyncStorage.setItem('someSavedState', JSON.stringify(this.state))
    return result
  } catch (e) {
    return null
  }
}

Затем в тот же день или через 2 секунды:

async componentWillMount() {
  try {
    const data = await AsyncStorage.getItem('someSavedState')
    const rehydrate = JSON.parse(data)
    return this.setState(rehydrate)
  } catch (e) {
    return null
  }
}

Вы также можете использовать Redux и передавать данные в дочерний компонент при рендеринге. Вы можете извлечь пользу из исследования состояния serializing и второго параметра функции Redux createStore который предназначен для регидратации исходного состояния.

Просто отметьте, что JSON.stringify() является дорогостоящей операцией, поэтому вам не следует делать это при нажатии клавиш и т.д. Если у вас есть проблемы, JSON.stringify().

Ответ 4

Я не эксперт в области React, но особенно ваш случай можно было решить очень чисто без каких-либо изменяемых объектов.

var StatefulView = React.createClass({
  getInitialState: function() {
    return {
      count: 0
    }
  },
  inc: function() {
    this.setState({count: this.state.count+1})
  },
  render: function() {
      return !this.props.show ? null : (
        <div>
          <button onClick={this.inc}>inc</button>
          <div>count:{this.state.count}</div>
        </div>
    )

  }
});

var App = React.createClass({
  getInitialState: function() {
    return {
      show: true,
      component: StatefulView
    }
  },
  toggle: function() {
    this.setState({show: !this.state.show})
  },
  render: function() {
    return (
      <div>
        <button onClick={this.toggle}>toggle</button>
        <this.state.component show={this.state.show}/>
      </div>
    )
  }
});

ReactDOM.render(
  <App/>,
  document.getElementById('container')
);

Вы можете увидеть его на jsfiddle.

Ответ 5

Я опаздываю на вечеринку, но если вы используете Redux. Вы получите это поведение почти из коробки с сокращением-persist. Просто добавьте его autoRehydrate в магазин, затем он будет прослушивать действия REHYDRATE, которые автоматически восстанавливают предыдущее состояние компонента (из веб-хранилища).

Ответ 6

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

  • Сохранение состояния в каком-либо магазине (Redux) или Context, вероятно, является лучшим решением.
  • Хранение в глобальной переменной будет работать только в том случае, если одновременно существует только ОДИН экземпляр вашего компонента. (Как каждый экземпляр узнает, какое сохраненное состояние принадлежит им?)
  • Сохранение в localStorage имеет те же предостережения, что и глобальная переменная, и, поскольку мы не говорим о восстановлении состояния при обновлении браузера, это, похоже, не приносит никакой пользы.

Ответ 7

Если вы хотите иметь возможность размонтировать и монтировать при сохранении состояния, вам нужно будет сохранить счетчик в App и передать счет через реквизиты.

(При этом вы должны вызывать функцию переключения внутри App, вам нужна функциональность, которая изменяет данные для работы с данными).

Я изменю вашу скрипту, чтобы быть функциональной, и обновить свой ответ.