Javascript 反应+;Alt:生命周期缺口

Javascript 反应+;Alt:生命周期缺口,javascript,reactjs,flux,alt.js,Javascript,Reactjs,Flux,Alt.js,编辑:查看git存储库以获得一个小示例: 我有一个组件RequireUser,它试图确保用户已登录,否则不会呈现其子项。它的父组件,App,应该知道是否需要用户,并在需要时提供登录表单 问题是,App组件安装在RequireUser组件之后的树中,如下所示: App RequireUser SomeOtherComponent 在RequireUser的componentDidMount中,我触发了一个动作requireLogin,该动作将UserStore的loginRequir

编辑:查看git存储库以获得一个小示例:

我有一个组件
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>
    );
  }
}
类RequireUser扩展了React.Component{
构造函数(){
超级();
this.state=alt.stores.UserStore.getState();
}
componentDidMount(){
this.unlisten=alt.stores.UserStore.listen(this.setState.bind(this));
如果(!this.state.requireUser){
UserActions.requireUser();
//使用setTimeout将起作用:
//setTimeout(()=>UserActions.requireUser());
}
}
组件将卸载(){
这是我的;
}
render(){
if(this.state.requireUser){
返回我已要求您的用户;
}
返回我将要求您的用户;
}
}
类应用程序扩展了React.Component{
构造函数(){
超级();
this.state=alt.stores.UserStore.getState();
}
componentDidMount(){
this.unlisten=alt.stores.UserStore.listen(this.setState.bind(this));
}
组件将卸载(){
这是我的;
}
render(){
返回(
需要用户?{this.state.requireUser+''}
);
}
}
输出:

用户要求?假的

我需要你的用户

如果在
RequireUser
中使用
setTimeout
App
接收状态更改并呈现,但仅在闪烁之后:

用户要求?真的

我需要你的用户


我觉得我所做的是一种反模式,如果能提供比使用setTimeout闪烁更优雅的解决方案,我将不胜感激。谢谢

我建议的答案是将其添加到应用程序组件中:

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的第二次渲染
  • 但是你的代码库仍然被认为是流动的,并且确实遵循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>;
         }
    }
    
    类RequireUser扩展了React.Component{
    render(){
    如果(此.props.requireUser){
    返回我已要求您的用户;
    }
    返回我将要求您的用户;
    }
    }
    
    然后,您将使应用程序组件成为控制器视图。监听器添加到这里,对状态的任何更改都会通过道具向下传播。现在我们可以在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>
           );
        }
    }
    
    类应用程序扩展了React.Component{
    (其他生命周期方法)
    组件willmount(){
    如果(!this.state.requireUser){
    UserActions.requireUser();
    }
    var userStoreState=alt.stores.UserStore.getState();
    this.setState(userStoreState);
    }
    componentDidMount(){
    (同上)
    }
    render(){
    返回(
    需要用户?{this.state.requireUser+''}
    );
    }
    }
    

    Flux体系结构和控制器视图/视图:

    您的每个组件仅从您的存储获取
    状态一次——仅在构建每个组件期间。这意味着组件中的
    状态将
    存储中的
    状态不同步


    在安装组件时,您需要在组件上设置
    存储
    侦听器,以便从
    存储
    中检索触发器和最新的
    状态
    。使用
    setState()
    更新组件内部的
    状态
    ,以便再次调用
    render()
    来呈现最新的
    状态

    将存储侦听器放入构造函数中如何?这对我很有效。

    多亏了托比,我已经试过了,并决定不使用它,因为埃斯林特建议不要在
    componentDidMount
    中使用
    setState
    。它触发了一个额外的渲染,所以它不是很好,不幸的是,即使它确实工作。还有其他想法吗?看起来componentWillMount()可能真的会起作用。根据docs,如果在此回调期间执行setState(),则这些更新只会执行一次渲染。你觉得怎么样?嘿,托比!不幸的是,在子组件的
    componentDidMount
    之前调用父组件的
    componentWillMount
    。在这里设置一个侦听器可能会起作用,但我对此不太感兴趣,因为这会导致一个备忘录