React TransitionGroup и React.cloneElement не отправляют обновленные реквизиты

Я следую руководству Чан Вана по созданию многоразовых переходов React с помощью HOC и ReactTransitionGroup( Часть 1 Часть 2) в сочетании с руководством Хуан Цзи по переходам страниц ( Ссылка).

Проблема, с которой я сталкиваюсь, заключается в том, что React.cloneElementпохоже, не передает обновленный реквизит одному из своих потомков, в то время как другие потомки правильно получают обновленные реквизиты.

Сначала немного кода:

TransitionContainer.js

TransitionContainer является компонентом контейнера, который сродни App в учебнике Хуан Цзи. Это вводит часть государства своим детям.

Дети TransitionGroup все это экземпляр HOC под названием Transition (код ниже)

import React from 'react';
import TransitionGroup from 'react-addons-transition-group';
import {connect} from 'react-redux';
class TransitionContainer extends React.Component{
  render(){
    console.log(this.props.transitionState);
    console.log("transitionContainer");
    return(
      <div>
      <TransitionGroup>
      {
        React.Children.map(this.props.children,
         (child) => React.cloneElement(child,      //These children are all instances of the Transition HOC
           { key: child.props.route.path + "//" + child.type.displayName,
             dispatch: this.props.dispatch,
             transitionState: this.props.transitionState
           }
         )
        )
      }

      </TransitionGroup>
      </div>
    )
  }
}
export default connect((state)=>({transitionState:state.transitions}),(dispatch)=>({dispatch:dispatch}))(TransitionContainer)

Transition.js

Transition это похоже на HOC Чанг Вана. Он принимает некоторые параметры, определяет componentWillEnter + componentWillLeave перехватывает и оборачивает компонент. TransitionContainer (выше) вводит props.transitionState в этот HOC. Тем не менее, иногда реквизиты не обновляются, даже если состояние меняется (см . Проблему ниже)

import React from 'react';
import getDisplayName from 'react-display-name';
import merge from 'lodash/merge'
import classnames from 'classnames'
import * as actions from './actions/transitions'
export function transition(WrappedComponent, options) {
  return class Transition extends React.Component {
    static displayName = `Transition(${getDisplayName(WrappedComponent)})`;
    constructor(props) {
      super(props);
      this.state = {
          willLeave:false,
          willEnter:false,
          key: options.key
      };
    }
    componentWillMount(){
      this.props.dispatch(actions.registerComponent(this.state.key))
    }
    componentWillUnmount(){
      this.props.dispatch(actions.destroyComponent(this.state.key))
    }
    resetState(){
      this.setState(merge(this.state,{
        willLeave: false,
        willEnter: false
      }));
    }
    doTransition(callback,optionSlice,willLeave,willEnter){
      let {transitionState,dispatch} = this.props;
      if(optionSlice.transitionBegin){
        optionSlice.transitionBegin(transitionState,dispatch)
      }
      if(willLeave){
        dispatch(actions.willLeave(this.state.key))
      }
      else if(willEnter){
        dispatch(actions.willEnter(this.state.key))
      }
      this.setState(merge(this.state,{
        willLeave: willLeave,
        willEnter: willEnter
      }));
      setTimeout(()=>{
        if(optionSlice.transitionComplete){
          optionSlice.transitionEnd(transitionState,dispatch);
        }
        dispatch(actions.transitionComplete(this.state.key))
        this.resetState();
        callback();
      },optionSlice.duration);
    }
    componentWillLeave(callback){
      this.doTransition(callback,options.willLeave,true,false)
    }
    componentWillEnter(callback){
      this.doTransition(callback,options.willEnter,false,true)
    }
    render() {

      console.log(this.props.transitionState);
      console.log(this.state.key);

      var willEnterClasses = options.willEnter.classNames
      var willLeaveClasses = options.willLeave.classNames
      var classes = classnames(
        {[willEnterClasses] : this.state.willEnter},
        {[willLeaveClasses] : this.state.willLeave},
      )
      return <WrappedComponent animationClasses={classes} {...this.props}/>
    }
  }
}

опции

Опции имеют следующую структуру:

{
  willEnter:{
    classNames : "a b c",
    duration: 1000,
    transitionBegin: (state,dispatch) => {//some custom logic.},
    transitionEnd: (state,dispatch) => {//some custom logic.}
         // I currently am not passing anything here, but I hope to make this a library
         // and am adding the feature to cover any use case that may require it.

  },
  willLeave:{
    classNames : "a b c",
    duration: 1000,
    transitionBegin: (state,dispatch) => {//some custom logic.},
    transitionEnd: (state,dispatch) => {//some custom logic.}

  }
}

Жизненный цикл перехода (onEnter или onLeave)

  • Когда компонент установлен, actions.registerComponent отправляется
    • componentWillMount
  • Когда компонент componentWillLeave или же componentWillEnter вызывается ловушка, соответствующая часть параметров отправляется doTransition
  • В doTransition:
    • Предоставленная пользователем функция transitionBegin вызывается (optionSlice.transitionBegin)
    • По умолчанию action.willLeave или же action.willEnter отправляется
    • Тайм-аут устанавливается на время анимации (optionSlice.duration). По истечении времени ожидания:
      • Предоставленная пользователем функция transitionEnd вызывается (optionSlice.transitionEnd)
      • По умолчанию actions.transitionComplete отправляется

По сути, optionSlice просто позволяет пользователю передавать некоторые параметры. optionSlice.transitionBegin а также optionSlice.transitionEnd это просто дополнительные функции, которые выполняются во время анимации, если это подходит для варианта использования. В настоящее время я ничего не передаю для своих компонентов, но я надеюсь вскоре сделать это библиотекой, так что я просто освещаю свои основы.

Почему я отслеживаю переходные состояния в любом случае?

В зависимости от элемента, который входит, анимация выхода изменяется, и наоборот.

Например, на изображении выше, когда синий входит, красный перемещается вправо, а когда синий выходит, красный перемещается влево. Однако, когда зеленый входит, красный движется влево, а когда зеленый выходит, красный движется вправо. Чтобы контролировать это, мне нужно знать состояние текущих переходов.

Эта проблема:

TransitionGroup содержит два элемента, один входящий, другой выходящий (управляется реагирующим маршрутизатором). Проходит опору под названием transitionState своим детям. Transition HOC (дети TransitionGroup) рассылает определенные избыточные действия в ходе анимации. Transition входящий компонент получает изменение реквизита, как и ожидалось, но выходящий компонент заморожен. Это реквизиты не меняются.

Это всегда тот, кто выходит, который не получает обновленные реквизиты. Я попытался переключить упакованные компоненты (выход и вход), и проблема не связана с упакованными компонентами.

Изображений

Экранный переход: переход

Переход в React DOM

Transition2

Выходящий компонент Transition(Connect(Home))) в этом случае не получает обновленные реквизиты.

Есть идеи, почему это так? Заранее спасибо за помощь.

Обновление 1:

import React from 'react';
import TransitionGroup from 'react-addons-transition-group';
import {connect} from 'react-redux';
var childFactoryMaker = (transitionState,dispatch) => (child) => {
  console.log(child)
  return React.cloneElement(child, {
    key: (child.props.route.path + "//" + child.type.displayName),
    transitionState: transitionState,
    dispatch: dispatch
  })
}

class TransitionContainer extends React.Component{
  render(){
    let{
      transitionState,
      dispatch,
      children
    } = this.props
    return(
      <div>
      <TransitionGroup childFactory={childFactoryMaker(transitionState,dispatch)}>
          {
            children
          }
      </TransitionGroup>
      </div>
    )
  }
}

export default connect((state)=>({transitionState:state.transitions}),(dispatch)=>({dispatch:dispatch}))(TransitionContainer)

Я обновил свой TransitionContainer к вышесказанному. Теперь componentWillEnter а также componentWillLeave крючки не называются. Я вошел React.cloneElement(child, {...}) в childFactory функция, и хуки (а также мои определенные функции, такие как doTransition) присутствуют в prototype приписывать. Только constructor, componentWillMount а также componentWillUnmount называются. Я подозреваю, что это потому, что key опора не вводится через React.cloneElement, transitionState а также dispatch впрыскивают все же

Обновление 2:

import React from 'react';
import TransitionGroup from 'react-addons-transition-group';
import {connect} from 'react-redux';
var childFactoryMaker = (transitionState,dispatch) => (child) => {
  console.log(React.cloneElement(child, {
    transitionState: transitionState,
    dispatch: dispatch
  }));
  return React.cloneElement(child, {
    key: (child.props.route.path + "//" + child.type.displayName),
    transitionState: transitionState,
    dispatch: dispatch
  })
}

class TransitionContainer extends React.Component{
  render(){
    let{
      transitionState,
      dispatch,
      children
    } = this.props
    return(
      <div>
      <TransitionGroup childFactory={childFactoryMaker(transitionState,dispatch)}>
      {
        React.Children.map(this.props.children,
            (child) => React.cloneElement(child,      //These children are all instances of the Transition HOC
                { key: child.props.route.path + "//" + child.type.displayName}
            )
        )
      }
      </TransitionGroup>
      </div>
    )
  }
}

export default connect((state)=>({transitionState:state.transitions}),(dispatch)=>({dispatch:dispatch}))(TransitionContainer)

После дальнейшей проверки источника TransitionGroup я понял, что поставил ключ не в том месте. Теперь все хорошо. Большое спасибо за помощь!!

1 ответ

Решение

Определение въезда и ухода детей

Представьте себе рендеринг образца JSX ниже:

<TransitionGroup>
  <div key="one">Foo</div>
  <div key="two">Bar</div>
</TransitionGroup>

<TransitionGroup>"s children опора будет состоять из элементов:

[
  { type: 'div', props: { key: 'one', children: 'Foo' }},
  { type: 'div', props: { key: 'two', children: 'Bar' }}
]

Вышеуказанные элементы будут сохранены как state.children, Затем мы обновляем <TransitionGroup> чтобы:

<TransitionGroup>
  <div key="two">Bar</div>
  <div key="three">Baz</div>
</TransitionGroup>

когда componentWillReceiveProps называется, его nextProps.children будет:

[
  { type: 'div', props: { key: 'two', children: 'Bar' }},
  { type: 'div', props: { key: 'three', children: 'Baz' }}
]

Сравнение state.children а также nextProps.childrenмы можем определить, что:

1.{ type: 'div', props: { key: 'one', children: 'Foo' }} уходит

2.{ type: 'div', props: { key: 'three', children: 'Baz' }} входит

В обычном приложении React это означает, что <div>Foo</div> больше не будет оказываться, но это не относится к детям <TransitionGroup>,

Как <TransitionGroup> Работает

Так как именно <TransitionGroup> возможность продолжить рендеринг компонентов, которые больше не существуют в props.children?

Какие <TransitionGroup> делает то, что он поддерживает children массив в своем состоянии. Всякий раз, когда <TransitionGroup> получает новые реквизиты, этот массив обновляется путем слияния текущего state.children и nextProps.children, (Первоначальный массив создается в constructor используя начальный children проп).

Теперь, когда <TransitionGroup> оказывает, это делает каждого ребенка в state.children массив. После того, как это оказало, это вызывает performEnter а также performLeave на любые входящие или выходящие дети. Это, в свою очередь, будет выполнять методы перехода компонентов.

После ухода компонента componentWillLeave метод (если он есть) завершил выполнение, он удалит себя из state.children массив, так что он больше не рендерится (при условии, что он не входил повторно, когда уходил).

Передача реквизита оставляющим детям?

Теперь вопрос в том, почему обновленные реквизиты не передаются уходящему элементу? Ну, как бы он получил реквизит? Реквизиты передаются от родительского компонента к дочернему компоненту. Если вы посмотрите на пример JSX выше, вы увидите, что выходящий элемент находится в отсоединенном состоянии. У него нет родителя, и он отображается только потому, что <TransitionGroup> хранит его в своем state,

Когда вы пытаетесь ввести состояние детей вашего <TransitionGroup> через React.cloneElementуходящий компонент не один из тех детей.

Хорошие новости

Вы можете передать childFactory поддержать вашу <TransitionGroup>, По умолчанию childFactory просто возвращает ребенка, но вы можете взглянуть на <CSSTransitionGroup> для более продвинутой детской фабрики.

Вы можете ввести правильные реквизиты в детей (даже оставляя) через эту дочернюю оболочку.

function childFactory(child) {
  return React.cloneElement(child, {
    transitionState,
    dispatch
  })
}

Использование:

var ConnectedTransitionGroup = connect(
  store => ({
   transitionState: state.transitions
  }),
  dispatch => ({ dispatch })
)(TransitionGroup)

render() {
  return (
    <ConnectedTransitionGroup childFactory={childFactory}>
      {children}
    </ConnectedTransitionGroup>
  )
}

React Transition Group была недавно отделена от основного репозитория React, и вы можете посмотреть ее исходный код здесь. Это довольно просто прочитать.

Другие вопросы по тегам