React + Alt:生命周期差距

React + Alt: Lifecycle Gap

编辑:查看 git 存储库中的一个最小示例: https://github.com/maximilianschmitt/blind-lifecycle

我有一个组件 RequireUser 试图确保用户已登录,否则不会呈现其子组件。它的父组件 App 应该知道是否需要用户并在需要时呈现登录表单。

问题是,App 组件在 RequireUser 组件之后挂载在树中,如下所示:

App
  RequireUser
    SomeOtherComponent

RequireUsercomponentDidMount 中,我正在触发一个操作 requireLogin,将 UserStoreloginRequired 变量设置为 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中使用setTimeoutApp接收状态变化并渲染,但仅在闪烁之后:

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 组件已经执行了两次渲染。

  1. RequireUser 的初始呈现
  2. componentDidMount() 回调
    • 一个动作被分派
  3. UserStore 接收分派的操作并更新其状态
    • 发出更改通知
  4. RequireUser 根据状态变化设置状态
  5. 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 一次 - 仅在每个组件的构造期间。这意味着组件中的 statesNOTstore

中的 states 同步

您需要在安装时在组件上设置 store 侦听器,以便从 store 和最新的 states 检索触发器。使用 setState() 更新组件内的 states,因此 render() 将再次调用以呈现最新的 states

将商店侦听器放在构造函数中怎么样?这对我有用。