React TransitionGroup和React.cloneElement不发送更新的props

16

我正在遵循Chang Wang的教程,使用HOC和ReactTransitionGroup第一部分 第二部分),结合Huan Ji的页面过渡教程(链接)进行可重复使用的React过渡动画制作。

我遇到的问题是,React.cloneElement似乎没有将更新的props传递给其中一个子组件,而其他子组件则可以正确地接收到更新的props。

首先,一些代码:

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。它需要一些选项,定义componentWillEntercomponentWillLeave钩子函数,并包装组件。TransitionContainer(上面提到的)将props.transitionState注入到这个HOC中。但是,有时候即使状态改变了,Props也不会更新(见下文的问题)。

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的prop传递给其子项。 Transition HOC(TransitionGroup的子项)通过整个动画过程中分派特定的redux操作。 输入的Transition组件按预期接收到更改的props,但退出的组件被冻结。 它的props不会更改。

始终是正在退出的组件未接收到更新的props。 我已经尝试切换包装组件(退出和进入),问题不是由包装组件引起的。

图片

屏幕上的转换效果: Transition

React DOM中的过渡效果

Transition2

在这种情况下,正在退出的组件Transition(Connect(Home)))没有接收到更新的props。

有什么想法是这种情况吗?感谢您提前的所有帮助。

更新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钩子没有被调用。我在childFactory函数中记录了React.cloneElement(child, {...}),发现钩子(以及我定义的函数,如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源代码后,我意识到我把key放错了地方。现在一切都好了。非常感谢您的帮助!!

1个回答

17

确定进入和离开的子元素

想象一下渲染以下JSX示例:

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

<TransitionGroup>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.childrennextProps.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> 收到新的 props 时,它会通过 合并 当前的 state.childrennextProps.children 来更新该数组。(初始数组是在构造函数中使用初始的 children prop 创建的。)
现在,当 <TransitionGroup> 渲染时,它会渲染 state.children 数组中的每个子组件。渲染完成后,它会在任何进入或离开的子组件上调用 performEnterperformLeave。这将依次执行组件的过渡方法。
在离开组件的 componentWillLeave 方法(如果有)执行完毕后,它将从 state.children 数组中移除自己,以便不再呈现(假设在离开时没有重新进入)。

向离开的子组件传递 props?

现在的问题是,为什么更新后的props没有传递给离开的元素?那么,它该如何接收props呢?Props从父组件传递到子组件。如果您查看上面的JSX示例,可以看到离开的元素处于分离状态。它没有父级,并且仅在<TransitionGroup>将其存储在state中时才会呈现。

当您尝试通过React.cloneElement将状态注入到<TransitionGroup>的子项中时,退出组件不是其中之一。

好消息

您可以向<TransitionGroup>传递一个childFactory属性。默认的childFactory只返回子级,但是您可以查看<CSSTransitionGroup>以获取更高级的子工厂

通过此子包装器,您可以将正确的props注入子项(甚至是离开的子项)。

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存储库中分离出来,您可以在此处查看其源代码。它非常简单易懂。


由于我需要在Transition HOC中访问状态,因此我自然会将其包装在connect()中。但是,问题在于TransitionGroup将停止工作,因为连接的Transition HOC现在不实现componentWillLeavecomponentWillEnter钩子。或者我没有看到什么?顺便说一句,谢谢@Paul S.提供的所有帮助。 - Jay S.
那确实使事情变得复杂了。你发送给转换组件的过渡状态是什么? - Paul S
只是哪些元素正在离开/进入。类似于 {home: "willLeave", bom: "willEnter", otherPage1:"transitionComplete"} 我这样做的原因是可以根据所采取的路线动态更改过渡效果。@PaulS - Jay S.
我在帖子中加入了更多细节(请参见“好消息”部分),但我认为您应该将childFactory属性传递给您的转换组,以将属性注入所有子元素。 - Paul S
某些原因导致关键字未被发送,componentWillEntercomponentWillLeave钩子未被调用(动画未被执行)。请参见我的更新@Paul S。 - Jay S.
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接