React + Alt: разрыв жизненного цикла

Редактировать: посмотрите минимальный пример в репозитории git: https://github.com/maximilianschmitt/blind-lifecycle

У меня есть компонент RequireUser, который пытается убедиться, что пользователь вошел в систему, и в противном случае не будет отображать своих дочерних элементов. Его родительский компонент, App, должен знать, требуется ли пользователь, и при необходимости отображать форму входа.

Проблема в том, что компонент App монтируется ПОСЛЕ компонента RequireUser в дереве следующим образом:

App
  RequireUser
    SomeOtherComponent

В componentDidMount RequireUser я запускаю действие requireLogin, которое устанавливает для переменной loginRequired UserStore значение 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>
    );
  }
}

Выход:

Требуется пользователь? ЛОЖЬ

мне нужен ваш пользователь

Если я использую setTimeout в RequireUser, App получает изменения состояния и визуализирует, но только после мерцания:

Требуется пользователь? истинный

мне нужен ваш пользователь

У меня такое ощущение, что то, что я делаю, является анти-шаблоном, и я был бы признателен за предложения более элегантного решения, чем мерцание с помощью setTimeout. Спасибо!


person wundermax    schedule 05.11.2015    source источник
comment
Есть ли у вас дальнейшие проблемы двигаться дальше? Если ваша проблема решена, вы должны отметить ответ, который помог решить проблему, как принятый. Это помогает другим людям найти то, что они ищут. Удачного кодирования :)   -  person Ling Zhong    schedule 09.11.2015
comment
Спасибо, Линг, я все еще смотрю, есть ли у кого-то решение, которое не потребует от меня рендеринга дважды. Я отмечу правильный ответ как принятый.   -  person wundermax    schedule 09.11.2015


Ответы (3)


Мой предлагаемый ответ - добавить это в компонент приложения:

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() callback
    • an action is dispatched
  3. UserStore receives the dispatched action and updates its state
    • change notification is emitted
  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>;
     }
}

Затем вы сделаете свой компонент приложения controller-view. Здесь добавляется прослушиватель, и любые изменения состояния распространяются вниз с помощью реквизита. Теперь мы можем настроить обратный вызов 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 и представления/представления контроллера: https://facebook.github.io/flux/docs/overview.html#views-and-controller-views

person Toby Liu    schedule 05.11.2015
comment
Спасибо, Тоби, я уже попробовал это и решил не использовать это, потому что eslint предлагает не использовать setState в componentDidMount. Он запускает дополнительный рендеринг, так что, к сожалению, это не так здорово, хотя и работает. Любые другие идеи? - person wundermax; 06.11.2015
comment
Похоже, что componentWillMount() действительно может помочь. Согласно документам, если вы выполняете setState() во время этого обратного вызова, рендеринг будет выполняться только один раз с этими обновлениями. facebook.github.io/react/docs/ Что вы думаете? - person Toby Liu; 06.11.2015
comment
Эй, Тоби! К сожалению, componentWillMount родительского компонента вызывается перед componentDidMount дочернего компонента. Настройка прослушивателя здесь, вероятно, сработает, но я не очень заинтересован в этом, так как это вызовет утечку памяти при рендеринге на стороне сервера. - person wundermax; 09.11.2015
comment
Хорошо, раз вы все еще на этом, я постараюсь помочь больше! По сути, вы не можете избежать этого двойного рендеринга. Тем не менее, вы можете поближе познакомиться с представлениями контроллеров как частью архитектуры Flux. У меня есть обновленный ответ для вас сейчас ... - person Toby Liu; 10.11.2015

Каждый ваш компонент получает states из вашего Магазина только один раз — только во время создания каждого компонента. Это означает, что states в ваших компонентах НЕ будет синхронизировано с states в store

Вам необходимо настроить прослушиватели store на ваших компонентах при монтировании, чтобы получить триггер из store и самую последнюю версию states. Используйте setState() для обновления states внутри компонента, поэтому render() будет вызываться снова для отображения актуального states.

person Ling Zhong    schedule 05.11.2015

Как насчет того, чтобы поместить прослушиватель хранилища в конструктор? Это сработало для меня.

person Hahn    schedule 05.11.2015