React + Alt:生命周期差距
React + Alt: Lifecycle Gap
编辑:查看 git 存储库中的一个最小示例: https://github.com/maximilianschmitt/blind-lifecycle
我有一个组件 RequireUser
试图确保用户已登录,否则不会呈现其子组件。它的父组件 App
应该知道是否需要用户并在需要时呈现登录表单。
问题是,App
组件在 RequireUser
组件之后挂载在树中,如下所示:
App
RequireUser
SomeOtherComponent
在 RequireUser
的 componentDidMount
中,我正在触发一个操作 requireLogin
,将 UserStore
的 loginRequired
变量设置为 true
.
这不会更新父组件 (App
),因为它尚未安装,因此无法将更改注册到商店。
class RequireUser extends React.Component {
constructor() {
super();
this.state = alt.stores.UserStore.getState();
}
componentDidMount() {
this.unlisten = alt.stores.UserStore.listen(this.setState.bind(this));
if (!this.state.requireUser) {
UserActions.requireUser();
// using setTimeout will work:
// setTimeout(() => UserActions.requireUser());
}
}
componentWillUnmount() {
this.unlisten();
}
render() {
if (this.state.requireUser) {
return <div>I have required your user</div>;
}
return <div>I will require your user</div>;
}
}
class App extends React.Component {
constructor() {
super();
this.state = alt.stores.UserStore.getState();
}
componentDidMount() {
this.unlisten = alt.stores.UserStore.listen(this.setState.bind(this));
}
componentWillUnmount() {
this.unlisten();
}
render() {
return (
<div>
<div>User required? {this.state.requireUser + ''}</div>
<RequireUser />
</div>
);
}
}
输出:
User required? false
I have required your user
如果我在RequireUser
中使用setTimeout
,App
接收状态变化并渲染,但仅在闪烁之后:
User required? true
I have required your user
我觉得我正在做的是一种反模式,如果能提供比使用 setTimeout 闪烁更优雅的解决方案的建议,我将不胜感激。谢谢!
我建议的答案是将其添加到 App 组件中:
componentDidMount() {
// setup listener for subsequent changes
alt.stores.UserStore.listen(this.onChange);
// grab the current state now that we're mounted
var userStoreState = alt.stores.UserStore.getState();
this.setState(userStoreState);
}
无法避免双重渲染。您的 RequireUser 组件已经执行了两次渲染。
- RequireUser 的初始呈现
- componentDidMount() 回调
- 一个动作被分派
- UserStore 接收分派的操作并更新其状态
- 发出更改通知
- RequireUser 根据状态变化设置状态
- RequireUser 的第二次渲染
但是您的代码库仍然被认为是 Flux,并且确实遵循了用于 React 应用程序的模式。本质上,你有一个加载状态......我们实际上不知道是否需要用户的状态。根据 UserActions.requireUser() 的作用,这可能需要也可能不需要。
您可以考虑重构
如果将 RequireUser 重写为 仅查看组件,则可以修复双重呈现。这意味着没有听众,也没有内部设置状态。该组件只是根据传入的道具呈现元素。这实际上是您的所有 RequireUser 组件:
class RequireUser extends React.Component {
render() {
if (this.props.requireUser) {
return <div>I have required your user</div>;
}
return <div>I will require your user</div>;
}
}
然后您将使您的 App 组件成为 controller-view。在这里添加了监听器,状态的任何变化都通过 props 向下传播。现在我们可以在 componentWillMount 回调中设置。这给了我们单一的渲染行为。
class App extends React.Component {
(other lifecycle methods)
componentWillMount() {
if (!this.state.requireUser) {
UserActions.requireUser();
}
var userStoreState = alt.stores.UserStore.getState();
this.setState(userStoreState);
}
componentDidMount() {
(same as above)
}
render() {
return (
<div>
<div>User required? {this.state.requireUser + ''}</div>
<RequireUser requireUser={this.state.requireUser} />
</div>
);
}
}
Flux 架构和controller-views/views:https://facebook.github.io/flux/docs/overview.html#views-and-controller-views
您的每个组件仅从您的商店 获得 states
一次 - 仅在每个组件的构造期间。这意味着组件中的 states
将 NOT 与 store
中的 states
同步
您需要在安装时在组件上设置 store
侦听器,以便从 store
和最新的 states
检索触发器。使用 setState()
更新组件内的 states
,因此 render()
将再次调用以呈现最新的 states
将商店侦听器放在构造函数中怎么样?这对我有用。
编辑:查看 git 存储库中的一个最小示例: https://github.com/maximilianschmitt/blind-lifecycle
我有一个组件 RequireUser
试图确保用户已登录,否则不会呈现其子组件。它的父组件 App
应该知道是否需要用户并在需要时呈现登录表单。
问题是,App
组件在 RequireUser
组件之后挂载在树中,如下所示:
App
RequireUser
SomeOtherComponent
在 RequireUser
的 componentDidMount
中,我正在触发一个操作 requireLogin
,将 UserStore
的 loginRequired
变量设置为 true
.
这不会更新父组件 (App
),因为它尚未安装,因此无法将更改注册到商店。
class RequireUser extends React.Component {
constructor() {
super();
this.state = alt.stores.UserStore.getState();
}
componentDidMount() {
this.unlisten = alt.stores.UserStore.listen(this.setState.bind(this));
if (!this.state.requireUser) {
UserActions.requireUser();
// using setTimeout will work:
// setTimeout(() => UserActions.requireUser());
}
}
componentWillUnmount() {
this.unlisten();
}
render() {
if (this.state.requireUser) {
return <div>I have required your user</div>;
}
return <div>I will require your user</div>;
}
}
class App extends React.Component {
constructor() {
super();
this.state = alt.stores.UserStore.getState();
}
componentDidMount() {
this.unlisten = alt.stores.UserStore.listen(this.setState.bind(this));
}
componentWillUnmount() {
this.unlisten();
}
render() {
return (
<div>
<div>User required? {this.state.requireUser + ''}</div>
<RequireUser />
</div>
);
}
}
输出:
User required? false
I have required your user
如果我在RequireUser
中使用setTimeout
,App
接收状态变化并渲染,但仅在闪烁之后:
User required? true
I have required your user
我觉得我正在做的是一种反模式,如果能提供比使用 setTimeout 闪烁更优雅的解决方案的建议,我将不胜感激。谢谢!
我建议的答案是将其添加到 App 组件中:
componentDidMount() {
// setup listener for subsequent changes
alt.stores.UserStore.listen(this.onChange);
// grab the current state now that we're mounted
var userStoreState = alt.stores.UserStore.getState();
this.setState(userStoreState);
}
无法避免双重渲染。您的 RequireUser 组件已经执行了两次渲染。
- RequireUser 的初始呈现
- componentDidMount() 回调
- 一个动作被分派
- UserStore 接收分派的操作并更新其状态
- 发出更改通知
- RequireUser 根据状态变化设置状态
- RequireUser 的第二次渲染
但是您的代码库仍然被认为是 Flux,并且确实遵循了用于 React 应用程序的模式。本质上,你有一个加载状态......我们实际上不知道是否需要用户的状态。根据 UserActions.requireUser() 的作用,这可能需要也可能不需要。
您可以考虑重构
如果将 RequireUser 重写为 仅查看组件,则可以修复双重呈现。这意味着没有听众,也没有内部设置状态。该组件只是根据传入的道具呈现元素。这实际上是您的所有 RequireUser 组件:
class RequireUser extends React.Component {
render() {
if (this.props.requireUser) {
return <div>I have required your user</div>;
}
return <div>I will require your user</div>;
}
}
然后您将使您的 App 组件成为 controller-view。在这里添加了监听器,状态的任何变化都通过 props 向下传播。现在我们可以在 componentWillMount 回调中设置。这给了我们单一的渲染行为。
class App extends React.Component {
(other lifecycle methods)
componentWillMount() {
if (!this.state.requireUser) {
UserActions.requireUser();
}
var userStoreState = alt.stores.UserStore.getState();
this.setState(userStoreState);
}
componentDidMount() {
(same as above)
}
render() {
return (
<div>
<div>User required? {this.state.requireUser + ''}</div>
<RequireUser requireUser={this.state.requireUser} />
</div>
);
}
}
Flux 架构和controller-views/views:https://facebook.github.io/flux/docs/overview.html#views-and-controller-views
您的每个组件仅从您的商店 获得 states
一次 - 仅在每个组件的构造期间。这意味着组件中的 states
将 NOT 与 store
states
同步
您需要在安装时在组件上设置 store
侦听器,以便从 store
和最新的 states
检索触发器。使用 setState()
更新组件内的 states
,因此 render()
将再次调用以呈现最新的 states
将商店侦听器放在构造函数中怎么样?这对我有用。