[英]How to keep React component state between mount/unmount?
我有一个简单的组件<StatefulView>
维护一个内部 state。我有另一个组件<App>
切换是否呈现<StatefulView>
。
但是,我想在安装/卸载之间保留<StatefulView>
的内部 state 。
我想我可以在<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 。
有没有办法在卸载后挂在同一个组件上以便重新安装?
由于您无法在卸载时将状态保留在组件本身中,因此您必须决定将其保存在何处。
这些是您的选择:
我一直在寻找一个简单的解决方案,其中数据在 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];
}
用法:
const [todos, setTodos] = useMemoryState('todos', ['Buy milk']);
好的。 所以和一堆人聊了聊,发现一个组件的实例是没有办法保存的。 因此,我们必须将其保存在其他地方。
1) 保存状态最明显的地方是在父组件内。
这对我来说不是一个选择,因为我试图从类似 UINavigationController 的对象中推送和弹出视图。
2)您可以将状态保存在其他地方,例如 Flux 商店或某个全局对象。
这对我来说也不是最好的选择,因为在哪个导航控制器中跟踪哪些数据属于哪个视图,这将是一场噩梦。
3)传递一个可变对象来保存和恢复状态。
这是我在 React 的 Github 存储库的各种问题单中发表评论时发现的一个建议。 这似乎是我要走的路,因为我可以创建一个可变对象并将其作为道具传递,然后使用相同的可变道具重新渲染相同的对象。
我实际上对其进行了一些修改以使其更加通用,并且我使用的是函数而不是可变对象。 我认为这更明智——不可变数据总是对我更可取。 这是我在做什么:
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)
})
}
就是这样。 它对我来说非常有效。 现在我可以将组件视为实例:)
对于那些刚刚在 2019 年或以后阅读本文的人,其他答案中已经给出了很多细节,但我想在这里强调几点:
嗯,如果 React Native,你可以使用localStorage
或AsyncStorage
。
反应网
componentWillUnmount() {
localStorage.setItem('someSavedState', JSON.stringify(this.state))
}
然后当天晚些时候或 2 秒后:
componentWillMount() {
const rehydrate = JSON.parse(localStorage.getItem('someSavedState'))
this.setState(rehydrate)
}
反应本机
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()
是一项昂贵的操作,因此您不应该在按键等上执行此操作。如果您有顾虑,请调查去抖动。
我迟到了,但如果你使用的是 Redux。 使用 redux-persist 几乎可以立即获得这种行为。 只需将它的autoRehydrate
添加到存储中,然后它就会监听REHYDRATE
操作, REHYDRATE
操作将自动恢复组件的先前状态(从 Web 存储)。
我不是 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看到它。
在渲染之间缓存状态的一种简单方法是使用模块在您正在处理的文件上导出表单闭包的事实。
使用useEffect
钩子可以指定在组件卸载时发生的逻辑(即更新在模块级别关闭的变量)。 这是有效的,因为导入的模块被缓存,这意味着在导入时创建的闭包永远不会消失。 我不确定这是否是一种好的方法,但在文件只导入一次的情况下有效(否则cachedState
将在默认呈现组件的所有实例之间共享)
var cachedState
export default () => {
const [state, setState] = useState(cachedState || {})
useEffect(() => {
return () => cachedState = state
})
return (...)
}
如果您希望能够在保持状态的同时卸载和挂载,则需要将计数存储在App
并通过 props 传递计数。
(这样做时,您应该在App
内部调用切换函数,您希望更改数据的功能与数据一起存在)。
我将修改您的小提琴以使其正常工作并用它更新我的答案。
为此,我制作了一个简单的 NPM 包。 你可以在这里找到它:
https://www.npmjs.com/package/react-component-state-cache
用法很简单。 首先,将组件包含在组件树中的某个位置,就像这样
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>
)
}
}
然后,您可以在任何组件中使用它,如下所示:
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)
就是这样。 十分简单。
我遇到了这篇文章,正在寻找一种随着时间的推移构建组件状态的方法,即后端的每个附加页面。
我使用持久器和 redux。 但是,对于这种状态,我希望将其保留在组件的本地。 结果是什么: useRef
。 我有点惊讶没有人提到它,所以我可能会错过问题的一个重要部分。 尽管如此, ref
在 React 的虚拟 DOM 的渲染之间仍然存在。
有了这个,我可以随着时间的推移构建我的缓存,观察组件更新(又名重新渲染),但不用担心“抛出”与组件相关的先前 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]);
通过跟踪随着缓存大小的增加而变化的本地状态,该组件被诱骗进行重新渲染。 我不会将 DOM 上的 ref 数据复制到组件的本地状态,只是某种摘要,例如长度。
这对于线程开启者的场景来说是不够的,但对于其他在这个线程上绊倒的人来说可能是不够的:根据您需要多少持久性以及您想要存储的数据有多复杂,查询参数可能也足够了。
例如,如果您只想在 window 调整大小操作之间保留一个 bool hideItems
,如果您 append 它将以my.example.com/myPage?hideItems=true
的方式保留。 您必须评估页面/组件内部呈现的参数,例如,使用NextJS
它将是
const router = useRouter()
const hide = router.query.hideItems
在我的例子中,我将 select 项目付款并保存到 redux 商店中的 state,当我的组件被卸载时,我想将 state 设置为新列表。 但是我有一个特殊情况,在我支付成功并重定向到另一个页面后,我不想保留我的旧数据,我这样使用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.