React TransitionGroup和React.cloneElement不发送更新的道具

时间:2016-12-31 00:03:18

标签: reactjs animation redux react-router reactcsstransitiongroup

我正在关注Chang Wang的教程,使用HOC和ReactTransitionGroupPart 1 Part 2)与Huan Ji的教程一起制作可重复使用的React过渡过渡(Link)。

我面临的问题是React.cloneElement似乎没有将更新的道具传递给其中一个孩子,而其他孩子正确地接收到更新的道具。

首先,一些代码:

TransitionContainer.js

TransitionContainer是一个容器组件,类似于Huan Ji教程中的App。它为其的孩子注入了一部分状态。

TransitionGroup的孩子都是名为Transition的HOC的实例(代码向下)

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类似于Chang Wang的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
  • 调用组件的componentWillLeavecomponentWillEnter挂钩时,相应的选项切片会发送到doTransition
  • 在doTransition中:
    • 调用用户提供的transitionBegin函数(optionSlice.transitionBegin
    • 默认action.willLeaveaction.willEnter已发送
    • 为动画的持续时间(optionSlice.duration)设置超时。超时完成后:
      • 调用用户提供的transitionEnd函数(optionSlice.transitionEnd
      • 默认actions.transitionComplete已分派

基本上,optionSlice只允许用户传入一些选项。 optionSlice.transitionBeginoptionSlice.transitionEnd只是在动画进行时执行的可选函数,如果它适合用例。我目前没有为我的组件传递任何东西,但我希望尽快将它变成一个库,所以我只是覆盖我的基础。

为什么我会跟踪转换状态?

enter image description here

根据输入的元素,退出动画会更改,反之亦然。

例如,在上图中,当蓝色进入时,红色向右移动,当蓝色退出时,红色向左移动。然而,当绿色进入时,红色向左移动,当绿色退出时,红色向右移动。为了控制这一点,我需要知道当前转换的状态。

问题:

TransitionGroup包含两个元素,一个进入,一个退出(由react-router控制)。它将一个叫transitionState的道具传给了它的孩子。 Transition HOC(TransitionGroup的子项)通过动画过程调度某些redux动作。正在进入的Transition组件会按预期更改道具,但会冻结正在退出的组件。它的道具不会改变。

永远是那个没有收到更新道具的人。我已经尝试切换包装的组件(退出和进入),问题不是由于包装的组件。

图片

屏幕转换: 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更新为上述内容。现在,componentWillEntercomponentWillLeave挂钩未被调用。我在React.cloneElement(child, {...})函数中记录了childFactory,并且doTransition属性中存在钩子(以及我定义的函数,如prototype)。仅调用constructorcomponentWillMountcomponentWillUnmount。我怀疑这是因为key道具没有通过React.cloneElement注入。 transitionStatedispatch正在注入。

更新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 个答案:

答案 0 :(得分:14)

确定进入和离开儿童

想象一下渲染下面的示例JSX:

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

<TransitionGroup>的{​​{1}}道具将由以下元素组成:

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,我们可以确定:

1。 nextProps.children正在离开

2。 { type: 'div', props: { key: 'one', children: 'Foo' }}正在进入。

在常规的React应用程序中,这意味着{ type: 'div', props: { key: 'three', children: 'Baz' }}将不再呈现,但<div>Foo</div>的子项不是这种情况。

<TransitionGroup>如何运作

那么<TransitionGroup>究竟能够继续呈现<TransitionGroup>中不再存在的组件吗?

props.children做的是它在其状态中维护<TransitionGroup>数组。每当children收到新道具时,此数组都会由merging当前<TransitionGroup>state.children更新。 (初始数组是使用初始nextProps.children道具在constructor中创建的。

现在,当children渲染时,它会渲染<TransitionGroup>数组中的每个子节点。呈现后,它会在任何进入或离开的孩子身上调用state.childrenperformEnter。这反过来将执行组件的转换方法。

离开组件的performLeave方法(如果有)已完成执行后,它将从componentWillLeave数组中删除它,使其不再呈现(假设它没有重新进入)当它离开时。)

通过道具离开孩子?

现在问题是,为什么没有更新的道具被传递给离开元素?那么,它会如何获得道具?道具从父组件传递到子组件。如果查看上面的示例JSX,您可以看到leave元素处于分离状态。它没有父级,只是因为state.children将其存储在<TransitionGroup>中而被渲染。

当您尝试将状态注入state<TransitionGroup>的子项时,离开组件不是其中一个孩子。

好消息

您可以将React.cloneElement道具传递给childFactory。默认<TransitionGroup>只会返回子项,但您可以查看childFactory以获取更多advanced child factory

您可以通过这个子包装器将正确的道具注入孩子们(甚至是留下的孩子)。

<CSSTransitionGroup>

用法:

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

React Transition Group最近在主要的React仓库中被拆分,您可以查看其源代码here。通读非常简单。