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