Javascript 如何在挂载/卸载之间保持 React 组件状态?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/31352261/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to keep React component state between mount/unmount?
提问by Chet
I have a simple component <StatefulView>
that maintains an internal state. I have another component <App>
that toggles whether or not <StatefulView>
is rendered.
我有一个<StatefulView>
维护内部状态的简单组件。我有另一个组件<App>
可以切换是否<StatefulView>
呈现。
However, I want to keep <StatefulView>
's internal state between mounting/unmouting.
但是,我想<StatefulView>
在安装/卸载之间保持的内部状态。
I figured I could instantiate the component in <App>
and then control whether its rendered/mounted.
我想我可以在其中实例化组件<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>
)
}
});
This apparently doesnt work and a new <StatefulView>
is created on each toggle.
这显然不起作用,<StatefulView>
每次切换都会创建一个新的。
Here's a JSFiddle.
这是一个JSFiddle。
Is there a way to hang on to the same component after it is unmounted so it can be re-mounted?
有没有办法在卸载后挂在同一个组件上以便重新安装?
回答by amann
The most important decision is where to keep the state when the component unmounts.
最重要的决定是在组件卸载时保持状态的位置。
Some options:
一些选项:
- React state in parent: If a parent component remains mounted, maybe it should be the owner of the state or could provide an initial state to an uncontrolled component below. You can pass the value back up before the component unmounts. With React context you can hoist the state to the very top of your app (see e.g. unstated).
- Outside of React: E.g. use-local-storage-state. Note that you might need to manually reset the state inbetween tests. Other options are query params in the URL, MobX, Redux, etc.
- React state in parent:如果父组件保持挂载,它可能应该是状态的所有者,或者可以为下面的不受控制的组件提供初始状态。您可以在卸载组件之前将值传递回来。使用 React 上下文,您可以将状态提升到应用程序的最顶部(参见例如unstated)。
- 在 React 之外:例如use-local-storage-state。请注意,您可能需要在测试之间手动重置状态。其他选项是 URL 中的查询参数、MobX、Redux 等。
I've you're looking for an easy solution where the data is persisted outside of React, this Hook might come in handy:
我一直在寻找一个简单的解决方案,其中数据在 React 之外持久化,这个 Hook 可能会派上用场:
const memoryState = {};
function useMemoryState(key, initialState) {
const [state, setState] = useState(() => {
const hasMemoryValue = Object.prototype.hasOwnProperty.call(memoryState, key);
if (hasMemoryValue) {
return memoryState[key]
} else {
return typeof initialState === 'function' ? initialState() : initialState;
}
});
function onChange(nextState) {
memoryState[key] = nextState;
setState(nextState);
}
return [state, onChange];
}
Usage:
用法:
const [todos, setTodos] = useMemoryState('todos', ['Buy milk']);
回答by Chet
OK. So after talking with a bunch of people, it turns out that there is no way to save an instance of a component. Thus, we HAVE to save it elsewhere.
好的。所以和一堆人聊了聊,发现一个组件的实例是没有办法保存的。因此,我们必须将其保存在其他地方。
1) The most obvious place to save the state is within the parent component.
1) 保存状态最明显的地方是在父组件内。
This isn't an option for me because I'm trying to push and pop views from a UINavigationController-like object.
这对我来说不是一个选择,因为我试图从类似 UINavigationController 的对象中推送和弹出视图。
2) You can save the state elsewhere, like a Flux store, or in some global object.
2)您可以将状态保存在其他地方,例如 Flux 商店或某个全局对象。
This also isn't the best option for me because it would be a nightmare keeping track of which data belongs to which view in which Navigation controller, etc.
这对我来说也不是最好的选择,因为在哪个导航控制器中跟踪哪些数据属于哪个视图,这将是一场噩梦。
3) Pass a mutable object to save and restore the state from.
3)传递一个可变对象来保存和恢复状态。
This was a suggestion I found while commenting in various issue tickets on React's Github repo. This seems to be the way to go for me because I can create a mutable object and pass it as props, then re-render the same object with the same mutable prop.
这是我在 React 的 Github 存储库的各种问题单中发表评论时发现的一个建议。这似乎是我要走的路,因为我可以创建一个可变对象并将其作为道具传递,然后使用相同的可变道具重新渲染相同的对象。
I've actually modified it a little to be more generalized and I'm using functions instead of mutable objects. I think this is more sane -- immutable data is always preferable to me. Here's what I'm doing:
我实际上对其进行了一些修改以使其更加通用,并且我使用的是函数而不是可变对象。我认为这更明智——不可变数据总是对我更可取。这是我在做什么:
function createInstance() {
var func = null;
return {
save: function(f) {
func = f;
},
restore: function(context) {
func && func(context);
}
}
}
Now in getInitialState
I'm creating a new instance for the component:
现在,getInitialState
我正在为组件创建一个新实例:
component: <StatefulView instance={createInstance()}/>
Then in the StatefulView
I just need to save and restore in componentWillMount
and componentWillUnmount
.
然后在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)
})
}
And that's it. It works really well for me. Now I can treat the components as if they're instances :)
就是这样。它对我来说非常有效。现在我可以将组件视为实例:)
回答by Arnel Enero
For those who are just reading this in 2019 or beyond, a lot of details have already been given in the other answers, but there are a couple of things that I want to emphasize here:
对于那些刚刚在 2019 年或以后阅读本文的人,其他答案中已经提供了很多细节,但我想在这里强调几点:
- Saving state in some store (Redux) or Context is probably the best solution.
- Storing in global variable will only work if there is ever only ONE instance of your component at a time. (How will each instance know which stored state is theirs?)
- Saving in localStorage has just the same caveats as global variable, and since we're not talking about restoring state on browser refresh, it doesn't seem to add any benefit.
- 在某个商店 (Redux) 或 Context 中保存状态可能是最好的解决方案。
- 仅当您的组件一次只有一个实例时,存储在全局变量中才有效。(每个实例如何知道哪个存储状态是他们的?)
- 在 localStorage 中保存与全局变量具有相同的警告,并且由于我们不是在谈论在浏览器刷新时恢复状态,因此它似乎没有增加任何好处。
回答by agm1984
Hmm, you could use localStorage
, or AsyncStorage
if React Native.
嗯,你可以使用localStorage
, 或者AsyncStorage
React Native。
React Web
反应网
componentWillUnmount() {
localStorage.setItem('someSavedState', JSON.stringify(this.state))
}
Then later that day or 2 seconds later:
然后当天晚些时候或 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
}
}
Then later that day or 2 seconds later:
然后当天晚些时候或 2 秒后:
async componentWillMount() {
try {
const data = await AsyncStorage.getItem('someSavedState')
const rehydrate = JSON.parse(data)
return this.setState(rehydrate)
} catch (e) {
return null
}
}
You could also use Redux
and pass the data into the child component when it renders. You might benefit from researching serializing
state and the second parameter of the Redux createStore
function which is for rehydrating an initial state.
您还可以Redux
在呈现时使用数据并将其传递给子组件。您可能会从研究serializing
状态和 ReduxcreateStore
函数的第二个参数中受益,该参数用于恢复初始状态。
Just note that JSON.stringify()
is an expensive operation, so you should not do it on keypress, etc. If you have concern, investigate debouncing.
请注意,这JSON.stringify()
是一项代价高昂的操作,因此您不应在按键等操作上执行此操作。如果您有疑虑,请调查去抖动。
回答by Yann VR
I'm late to the party but if you're using Redux. You'll get that behaviour almost out of the box with redux-persist. Just add its autoRehydrate
to the store then it will be listening to REHYDRATE
actions that will automatically restore previous state of the component (from web storage).
我迟到了,但如果你使用的是 Redux。使用 redux-persist 几乎可以立即获得这种行为。只需将其添加autoRehydrate
到商店,然后它就会监听REHYDRATE
将自动恢复组件先前状态的操作(从网络存储)。
回答by James Akwuh
I'm not an expert in React but particularly your case could be solved very cleanly without any mutable objects.
我不是 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')
);
You can see it at jsfiddle.
你可以在jsfiddle看到它。
回答by ChadF
If you want to be able to unmount and mount while maintaining state, you will need to store the count in App
and pass down the count via props.
如果您希望能够在保持状态的同时卸载和挂载,则需要将计数存储在其中App
并通过 props 传递计数。
(When doing this, you should be calling a toggle function inside of App
, you want the functionality which changes data to live with the data).
(这样做时,您应该在 内部调用切换函数App
,您希望更改数据的功能与数据一起存在)。
I will modify your fiddle to be functional and update my answer with it.
我将修改您的小提琴以使其正常工作并用它更新我的答案。