[英]How to keep React component state between mount/unmount?
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>
维护一个内部 state。我有另一个组件<App>
切换是否呈现<StatefulView>
。
However, I want to keep <StatefulView>
's internal state between mounting/unmouting.但是,我想在安装/卸载之间保留<StatefulView>
的内部 state 。
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?有没有办法在卸载后挂在同一个组件上以便重新安装?
Since you can't keep the state in the component itself when it unmounts, you have to decide where else it should be saved.由于您无法在卸载时将状态保留在组件本身中,因此您必须决定将其保存在何处。
These are your options:这些是您的选择:
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']);
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.这是我在 React 的 Github 存储库的各种问题单中发表评论时发现的一个建议。 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.这似乎是我要走的路,因为我可以创建一个可变对象并将其作为道具传递,然后使用相同的可变道具重新渲染相同的对象。
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 :)现在我可以将组件视为实例:)
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 年或以后阅读本文的人,其他答案中已经给出了很多细节,但我想在这里强调几点:
Hmm, you could use localStorage
, or AsyncStorage
if React Native.嗯,如果 React Native,你可以使用localStorage
或AsyncStorage
。
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.您还可以使用Redux
并在呈现时将数据传递给子组件。 You might benefit from researching serializing
state and the second parameter of the Redux createStore
function which is for rehydrating an initial state.您可能会从研究serializing
状态和 Redux createStore
函数的第二个参数中受益,该参数用于恢复初始状态。
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()
是一项昂贵的操作,因此您不应该在按键等上执行此操作。如果您有顾虑,请调查去抖动。
I'm late to the party but if you're using Redux.我迟到了,但如果你使用的是 Redux。 You'll get that behaviour almost out of the box with redux-persist.使用 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).只需将它的autoRehydrate
添加到存储中,然后它就会监听REHYDRATE
操作, REHYDRATE
操作将自动恢复组件的先前状态(从 Web 存储)。
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')
);
An easy way of caching state between renders would be to use the fact that modules exported form closure over the file that you are working in.在渲染之间缓存状态的一种简单方法是使用模块在您正在处理的文件上导出表单闭包的事实。
Using a useEffect
hook you can specify logic that happens on component dismount (ie update a variable that is closed over at the module level).使用useEffect
钩子可以指定在组件卸载时发生的逻辑(即更新在模块级别关闭的变量)。 This works because imported modules are cached, meaning that the closure created on import never disappears.这是有效的,因为导入的模块被缓存,这意味着在导入时创建的闭包永远不会消失。 I'm not sure if this is a GOOD approach or not, but works in cases where a file is only imported once (otherwise the cachedState
would be shared across all instances of the default rendered component)我不确定这是否是一种好的方法,但在文件只导入一次的情况下有效(否则cachedState
将在默认呈现组件的所有实例之间共享)
var cachedState
export default () => {
const [state, setState] = useState(cachedState || {})
useEffect(() => {
return () => cachedState = state
})
return (...)
}
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.我将修改您的小提琴以使其正常工作并用它更新我的答案。
I've made a simple NPM package exactly for this purpose.为此,我制作了一个简单的 NPM 包。 You can find it here:你可以在这里找到它:
https://www.npmjs.com/package/react-component-state-cache https://www.npmjs.com/package/react-component-state-cache
Usage is simple.用法很简单。 First, include the component somewhere high up in your component tree, like this首先,将组件包含在组件树中的某个位置,就像这样
import React from 'react'
import {ComponentStateCache} from 'react-component-state-cache'
import {Provider} from 'react-redux' // for example
import MyApp from './MyApp.js'
class AppWrapper extends React.Component {
render() {
return (
<Provider store={this.store}>
<ComponentStateCache>
<MyApp />
</ComponentStateCache>
</Provider>
)
}
}
then, you can use it in any of your components as follows:然后,您可以在任何组件中使用它,如下所示:
import React from 'react'
import { withComponentStateCache } from 'react-component-state-cache'
class X extends React.Component {
constructor(props) {
super(props)
this.state = {
// anything, really.
}
}
componentDidMount() {
// Restore the component state as it was stored for key '35' in section 'lesson'
//
// You can choose 'section' and 'key' freely, of course.
this.setState(this.props.componentstate.get('lesson', 35))
}
componentDidUnmount() {
// store this state's component in section 'lesson' with key '35'
this.props.componentstate.set('lesson', 35, this.state)
}
}
export default withComponentStateCache(X)
That's it.就是这样。 Easy peasy.十分简单。
I came across this post looking for a way to build the component state over time, ie, with each additional page from the backend.我遇到了这篇文章,正在寻找一种随着时间的推移构建组件状态的方法,即后端的每个附加页面。
I use a persistor and redux.我使用持久器和 redux。 However, for this state I wanted to keep it local to the component.但是,对于这种状态,我希望将其保留在组件的本地。 What ended up working: useRef
.结果是什么: useRef
。 I'm a bit surprised no one mentioned it so I could be missing an important part of the question.我有点惊讶没有人提到它,所以我可能会错过问题的一个重要部分。 Notwithstanding, the ref
persists between renders of React's virtual DOM.尽管如此, ref
在 React 的虚拟 DOM 的渲染之间仍然存在。
With that in place, I can build my cache over-time, watch the component update (aka re-render), but not worry about "throwing-out" previous api data that remains relevant for the component.有了这个,我可以随着时间的推移构建我的缓存,观察组件更新(又名重新渲染),但不用担心“抛出”与组件相关的先前 api 数据。
const cacheRef = useRef([]);
const [page, setPage] = useState(() => 0);
// this could be part of the `useEffect` hook instead as I have here
const getMoreData = useCallback(
async (pageProp, limit = 15) => {
const newData = await getData({
sources,
page: pageProp,
limit,
});
cacheRef.current = [...(cacheRef.current || []), ...newData];
},
[page, sources],
);
useEffect(() => {
const atCacheCapacity = cacheRef.current.length >= MAX;
if (!atCacheCapacity) {
getMoreData(page + 1);
setPage(page + 1);
}
}, [MAX, getMoreData, page]);
The component is tricked into re-rendering by tracking a local state that changes with the increasing size of the cache.通过跟踪随着缓存大小的增加而变化的本地状态,该组件被诱骗进行重新渲染。 I don't copy the ref data on the DOM into the component's local state, just some sort of summary eg, length.我不会将 DOM 上的 ref 数据复制到组件的本地状态,只是某种摘要,例如长度。
This won't suffice for the scenario of the thread opener, but maybe for other people stumbling on this thread: Depending on how much persistence you need and how complex the data is you want to store, a query parameter might also suffice.这对于线程开启者的场景来说是不够的,但对于其他在这个线程上绊倒的人来说可能是不够的:根据您需要多少持久性以及您想要存储的数据有多复杂,查询参数可能也足够了。
For example, if you just want to keep a bool hideItems
between window resizing operations, it will retain if you append it in the manner of my.example.com/myPage?hideItems=true
.例如,如果您只想在 window 调整大小操作之间保留一个 bool hideItems
,如果您 append 它将以my.example.com/myPage?hideItems=true
的方式保留。 You've got to evaluate the parameter on rendering inside you page/component, eg with NextJS
it would be您必须评估页面/组件内部呈现的参数,例如,使用NextJS
它将是
const router = useRouter()
const hide = router.query.hideItems
In my case, I select items to payment and save it to state in redux store, when my component is unmout, I want to set state to new list.在我的例子中,我将 select 项目付款并保存到 redux 商店中的 state,当我的组件被卸载时,我想将 state 设置为新列表。 But I have a special case after I payment success and redirect to another page, I don't want to keep my old data, I use useRef
like this但是我有一个特殊情况,在我支付成功并重定向到另一个页面后,我不想保留我的旧数据,我这样使用useRef
const isPayment = useRef(false) useEffect(() => { return () => { if(.isPayment,current){ //if my handlePayment is called, // I won't run my handle when component is unmout Your action when component is unmout } } }.[]) const handlePayment = () => { isPayment.current = true }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.